use native_ossl_sys as sys;
use std::ffi::CStr;
use std::fmt;
#[derive(Debug, Clone)]
pub struct Error {
code: u64,
reason: Option<String>,
lib: Option<String>,
file: Option<String>,
func: Option<String>,
data: Option<String>,
}
impl Error {
#[must_use]
pub fn code(&self) -> u64 {
self.code
}
#[must_use]
pub fn lib(&self) -> Option<&str> {
self.lib.as_deref()
}
#[must_use]
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
#[must_use]
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
#[must_use]
pub fn func(&self) -> Option<&str> {
self.func.as_deref()
}
#[must_use]
pub fn data(&self) -> Option<&str> {
self.data.as_deref()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(r) = &self.reason {
write!(f, "{r}")?;
} else {
write!(f, "error:{:#010x}", self.code)?;
}
if let Some(lib) = &self.lib {
write!(f, " (lib:{lib})")?;
}
if let Some(func) = &self.func {
write!(f, " in {func}")?;
}
if let Some(data) = &self.data {
write!(f, ": {data}")?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ErrorStack(Vec<Error>);
impl ErrorStack {
#[must_use]
pub fn drain() -> Self {
let mut errors = Vec::new();
loop {
let mut file: *const std::os::raw::c_char = std::ptr::null();
let mut func: *const std::os::raw::c_char = std::ptr::null();
let mut data: *const std::os::raw::c_char = std::ptr::null();
let mut line: std::os::raw::c_int = 0;
let mut flags: std::os::raw::c_int = 0;
let code = unsafe {
sys::ERR_get_error_all(
std::ptr::addr_of_mut!(file),
std::ptr::addr_of_mut!(line),
std::ptr::addr_of_mut!(func),
std::ptr::addr_of_mut!(data),
std::ptr::addr_of_mut!(flags),
)
};
if code == 0 {
break;
}
let reason = unsafe {
let p = sys::ERR_reason_error_string(code);
if p.is_null() {
None
} else {
Some(CStr::from_ptr(p).to_string_lossy().into_owned())
}
};
let lib_name = unsafe {
let p = sys::ERR_lib_error_string(code);
if p.is_null() {
None
} else {
Some(CStr::from_ptr(p).to_string_lossy().into_owned())
}
};
let file_str = unsafe {
if file.is_null() {
None
} else {
Some(CStr::from_ptr(file).to_string_lossy().into_owned())
}
};
let func_str = unsafe {
if func.is_null() {
None
} else {
Some(CStr::from_ptr(func).to_string_lossy().into_owned())
}
};
let data_str = unsafe {
if data.is_null() || (flags & 0x02) == 0 {
None
} else {
Some(CStr::from_ptr(data).to_string_lossy().into_owned())
}
};
errors.push(Error {
code,
reason,
lib: lib_name,
file: file_str,
func: func_str,
data: data_str,
});
}
ErrorStack(errors)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn errors(&self) -> impl Iterator<Item = &Error> {
self.0.iter()
}
}
impl fmt::Display for ErrorStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, e) in self.0.iter().enumerate() {
if i > 0 {
f.write_str("; ")?;
}
fmt::Display::fmt(e, f)?;
}
Ok(())
}
}
impl std::error::Error for ErrorStack {}
#[cfg(ossl320)]
pub struct ErrState {
ptr: *mut sys::ERR_STATE,
}
#[cfg(ossl320)]
impl ErrState {
#[must_use]
pub fn capture() -> Option<Self> {
let ptr = unsafe { sys::OSSL_ERR_STATE_new() };
if ptr.is_null() {
return None;
}
unsafe { sys::OSSL_ERR_STATE_save(ptr) };
Some(ErrState { ptr })
}
#[must_use]
pub fn restore_and_drain(self) -> ErrorStack {
unsafe { sys::OSSL_ERR_STATE_restore(self.ptr) };
let ptr = self.ptr;
std::mem::forget(self);
unsafe { sys::OSSL_ERR_STATE_free(ptr) };
ErrorStack::drain()
}
}
#[cfg(ossl320)]
impl Drop for ErrState {
fn drop(&mut self) {
unsafe { sys::OSSL_ERR_STATE_free(self.ptr) };
}
}
#[cfg(ossl320)]
unsafe impl Send for ErrState {}
#[macro_export]
macro_rules! ossl_call {
($expr:expr) => {{
#[allow(clippy::macro_metavars_in_unsafe)]
let rc = unsafe { $expr };
if rc == 1 {
Ok(())
} else {
Err($crate::error::ErrorStack::drain())
}
}};
}
#[macro_export]
macro_rules! ossl_ptr {
($expr:expr) => {{
#[allow(clippy::macro_metavars_in_unsafe)]
let ptr = unsafe { $expr };
if ptr.is_null() {
Err($crate::error::ErrorStack::drain())
} else {
Ok(ptr)
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drain_empty_queue_gives_empty_stack() {
unsafe { sys::ERR_clear_error() };
let stack = ErrorStack::drain();
assert!(stack.is_empty());
}
#[test]
fn failed_fetch_populates_error_stack() {
unsafe { sys::ERR_clear_error() };
let ptr = unsafe {
sys::EVP_MD_fetch(
std::ptr::null_mut(),
c"NONEXISTENT_ALGO_XYZ".as_ptr(),
std::ptr::null(),
)
};
assert!(ptr.is_null());
let stack = ErrorStack::drain();
assert!(!stack.is_empty(), "expected at least one error record");
let second = ErrorStack::drain();
assert!(second.is_empty());
}
#[cfg(ossl320)]
#[test]
fn err_state_round_trip() {
unsafe { sys::ERR_clear_error() };
unsafe {
sys::EVP_MD_fetch(
std::ptr::null_mut(),
c"NONEXISTENT_ALGO_XYZ".as_ptr(),
std::ptr::null(),
);
}
let state = ErrState::capture().expect("OSSL_ERR_STATE_new failed");
let stack = state.restore_and_drain();
assert!(!stack.is_empty());
}
}