use std::ffi::CStr;
use std::fmt;
use std::io;
use std::os::raw::c_int;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Init(String),
Handshake(String),
Io(io::Error),
Ktls(KtlsError),
}
#[derive(Debug)]
#[non_exhaustive]
pub enum KtlsError {
Unsupported,
IneligibleCipher { tls_version: String, cipher: String },
BufferedPlaintext(usize),
TlsUlpUnavailable(io::Error),
SocketUnattachable(io::Error),
SetSockOpt(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Init(m) => write!(f, "tls init: {m}"),
Self::Handshake(m) => write!(f, "tls handshake: {m}"),
Self::Io(e) => write!(f, "tls io: {e}"),
Self::Ktls(e) => write!(f, "ktls: {e}"),
}
}
}
impl fmt::Display for KtlsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported => f.write_str("kTLS is not supported on this platform"),
Self::IneligibleCipher {
tls_version,
cipher,
} => write!(
f,
"negotiated session is not kTLS-eligible ({tls_version} / {cipher})"
),
Self::BufferedPlaintext(n) => write!(
f,
"libssl has {n} bytes of buffered plaintext; kTLS install would lose them"
),
Self::TlsUlpUnavailable(e) => write!(
f,
"kernel `tls` ULP unavailable ({e}); load it with `modprobe tls` \
or ensure the host kernel was built with CONFIG_TLS"
),
Self::SocketUnattachable(e) => write!(
f,
"kernel rejected TCP_ULP attach on this socket ({e}); \
typically a transient race with TCP teardown"
),
Self::SetSockOpt(e) => write!(f, "setsockopt failed: {e}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Ktls(e) => Some(e),
Self::Init(_) | Self::Handshake(_) => None,
}
}
}
impl std::error::Error for KtlsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::SetSockOpt(e) | Self::TlsUlpUnavailable(e) | Self::SocketUnattachable(e) => {
Some(e)
}
Self::Unsupported | Self::IneligibleCipher { .. } | Self::BufferedPlaintext(_) => None,
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[must_use]
pub fn last_error() -> String {
let code = unsafe { aws_lc_sys::ERR_get_error() };
if code == 0 {
return "(no error queued)".into();
}
let mut buf = [0u8; 256];
unsafe {
aws_lc_sys::ERR_error_string_n(code, buf.as_mut_ptr().cast(), buf.len());
}
unsafe {
aws_lc_sys::ERR_clear_error();
}
let cstr = CStr::from_bytes_until_nul(&buf).unwrap_or(c"(malformed error string)");
cstr.to_string_lossy().into_owned()
}
pub(crate) fn pem_eof_or_err(label: &str) -> Result<()> {
let packed = unsafe { aws_lc_sys::ERR_peek_last_error() };
if packed == 0 {
return Ok(());
}
#[allow(clippy::cast_possible_wrap)]
let lib = ((packed >> 24) & 0xff) as c_int;
#[allow(clippy::cast_possible_wrap)]
let reason = (packed & 0xfff) as c_int;
if lib == aws_lc_sys::ERR_LIB_PEM && reason == aws_lc_sys::PEM_R_NO_START_LINE {
unsafe {
aws_lc_sys::ERR_clear_error();
}
Ok(())
} else {
Err(Error::Init(format!("{label}: {}", last_error())))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_queue_returns_sentinel() {
unsafe {
aws_lc_sys::ERR_clear_error();
}
assert_eq!(last_error(), "(no error queued)");
}
#[test]
fn display_includes_inner_message() {
let err = Error::Init("bad cert".into());
assert!(format!("{err}").contains("bad cert"));
}
#[test]
fn ktls_unsupported_display() {
let err = Error::Ktls(KtlsError::Unsupported);
let s = format!("{err}");
assert!(s.contains("ktls"), "got: {s}");
assert!(s.contains("not supported"), "got: {s}");
}
#[test]
fn io_error_round_trip_via_from() {
let io_err = io::Error::new(io::ErrorKind::ConnectionReset, "reset");
let e: Error = io_err.into();
assert!(matches!(e, Error::Io(_)));
}
#[test]
fn handshake_display() {
let s = format!("{}", Error::Handshake("bad alert".into()));
assert!(s.starts_with("tls handshake:"), "got: {s}");
assert!(s.contains("bad alert"), "got: {s}");
}
#[test]
fn io_display_wraps_inner() {
let inner = io::Error::new(io::ErrorKind::ConnectionReset, "boom");
let s = format!("{}", Error::Io(inner));
assert!(s.starts_with("tls io:"), "got: {s}");
assert!(s.contains("boom"), "got: {s}");
}
#[test]
fn ktls_ineligible_cipher_display() {
let e = Error::Ktls(KtlsError::IneligibleCipher {
tls_version: "TLSv1.2".into(),
cipher: "ECDHE-ECDSA-AES128-SHA".into(),
});
let s = format!("{e}");
assert!(s.contains("TLSv1.2"), "got: {s}");
assert!(s.contains("ECDHE-ECDSA-AES128-SHA"), "got: {s}");
}
#[test]
fn ktls_buffered_plaintext_display() {
let e = KtlsError::BufferedPlaintext(17);
let s = format!("{e}");
assert!(s.contains("17"), "got: {s}");
}
#[test]
fn ktls_setsockopt_display_and_source() {
let inner = io::Error::from_raw_os_error(13); let ke = KtlsError::SetSockOpt(inner);
let s = format!("{ke}");
assert!(s.starts_with("setsockopt failed:"), "got: {s}");
assert!(std::error::Error::source(&ke).is_some());
let e = Error::Ktls(KtlsError::SetSockOpt(io::Error::from_raw_os_error(13)));
assert!(std::error::Error::source(&e).is_some());
}
#[test]
fn ktls_tls_ulp_unavailable_display_mentions_modprobe() {
let inner = io::Error::from_raw_os_error(2); let ke = KtlsError::TlsUlpUnavailable(inner);
let s = format!("{ke}");
assert!(s.contains("modprobe tls"), "got: {s}");
assert!(s.contains("CONFIG_TLS"), "got: {s}");
assert!(std::error::Error::source(&ke).is_some());
}
#[test]
fn error_source_io_some_init_none() {
let io_err = Error::Io(io::Error::other("x"));
assert!(std::error::Error::source(&io_err).is_some());
let init = Error::Init("x".into());
assert!(std::error::Error::source(&init).is_none());
let hs = Error::Handshake("x".into());
assert!(std::error::Error::source(&hs).is_none());
}
#[test]
fn ktls_error_source_only_for_setsockopt() {
assert!(std::error::Error::source(&KtlsError::Unsupported).is_none());
assert!(std::error::Error::source(&KtlsError::BufferedPlaintext(1)).is_none());
assert!(std::error::Error::source(&KtlsError::IneligibleCipher {
tls_version: "x".into(),
cipher: "y".into(),
})
.is_none());
assert!(std::error::Error::source(&KtlsError::TlsUlpUnavailable(
io::Error::from_raw_os_error(2)
))
.is_some());
}
}