use std::ffi::c_char;
use std::sync::atomic::{AtomicU64, Ordering};
pub const SYNQRO_MAX_INPUT_LEN: usize = 4096;
static GLOBAL_ERROR_COUNTER: AtomicU64 = AtomicU64::new(1);
pub fn next_error_id() -> u64 {
GLOBAL_ERROR_COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Debug, thiserror::Error)]
pub enum SynqroError {
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("cryptographic error: {0}")]
Crypto(String),
#[error("network error: {0}")]
Network(String),
#[error("signature verification failed: {0}")]
SignatureVerification(String),
#[error("rollback blocked: {0}")]
RollbackBlocked(String),
#[error("permission denied: {0}")]
Permission(String),
#[error("configuration error: {0}")]
Config(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("YAML error: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("internal error")]
Internal(String),
#[error("signature verification failed: {0}")]
Signature(String),
#[error("rollback failed: {0}")]
Rollback(String),
#[error("engine in degraded mode — check audit log and reset")]
Degraded,
#[error("keychain error: {0}")]
Keychain(String),
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SynqroStatus {
SynqroOk = 0,
SynqroErrInvalidInput = 1,
SynqroErrCrypto = 2,
SynqroErrNetwork = 3,
SynqroErrSignature = 4,
SynqroErrRollback = 5,
SynqroErrPermission = 6,
SynqroErrInternal = 99,
}
impl From<&SynqroError> for SynqroStatus {
fn from(err: &SynqroError) -> Self {
match err {
SynqroError::InvalidInput(_) => SynqroStatus::SynqroErrInvalidInput,
SynqroError::Crypto(_) => SynqroStatus::SynqroErrCrypto,
SynqroError::Network(_) => SynqroStatus::SynqroErrNetwork,
SynqroError::SignatureVerification(_) => SynqroStatus::SynqroErrSignature,
SynqroError::RollbackBlocked(_) => SynqroStatus::SynqroErrRollback,
SynqroError::Permission(_) => SynqroStatus::SynqroErrPermission,
SynqroError::Config(_)
| SynqroError::Io(_)
| SynqroError::Json(_)
| SynqroError::Yaml(_)
| SynqroError::Internal(_)
| SynqroError::Degraded => SynqroStatus::SynqroErrInternal,
SynqroError::Signature(_) => SynqroStatus::SynqroErrSignature,
SynqroError::Rollback(_) => SynqroStatus::SynqroErrRollback,
SynqroError::Keychain(_) => SynqroStatus::SynqroErrPermission,
}
}
}
impl From<SynqroError> for SynqroStatus {
fn from(err: SynqroError) -> Self {
SynqroStatus::from(&err)
}
}
#[repr(C)]
pub struct SynqroResult {
pub status: SynqroStatus,
pub message: *const c_char,
pub error_id: u64,
}
unsafe impl Send for SynqroResult {}
unsafe impl Sync for SynqroResult {}
impl SynqroResult {
pub fn ok() -> Self {
Self {
status: SynqroStatus::SynqroOk,
message: std::ptr::null(),
error_id: 0,
}
}
pub fn err(status: SynqroStatus, msg: &str, id: u64) -> Self {
let sanitised: String = msg
.chars()
.map(|c| if c == '\0' { '?' } else { c })
.collect();
let cstring = std::ffi::CString::new(sanitised).unwrap_or_else(|_| {
std::ffi::CString::new("(message encoding error)").expect("static literal is valid")
});
Self {
status,
message: cstring.into_raw(),
error_id: id,
}
}
pub fn from_error(err: &SynqroError) -> Self {
let status = SynqroStatus::from(err);
let id = next_error_id();
let msg = match err {
SynqroError::Internal(_)
| SynqroError::Config(_)
| SynqroError::Io(_)
| SynqroError::Json(_)
| SynqroError::Yaml(_) => {
format!("an internal error occurred (id={})", id)
}
other => format!("{}", other),
};
Self::err(status, &msg, id)
}
}
pub fn validate_input(s: &str) -> Result<(), SynqroError> {
if s.len() > SYNQRO_MAX_INPUT_LEN {
return Err(SynqroError::InvalidInput(format!(
"input length {} exceeds maximum of {} bytes",
s.len(),
SYNQRO_MAX_INPUT_LEN
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_input_accepts_empty() {
assert!(validate_input("").is_ok());
}
#[test]
fn validate_input_accepts_max_length() {
let s = "a".repeat(SYNQRO_MAX_INPUT_LEN);
assert!(validate_input(&s).is_ok());
}
#[test]
fn validate_input_rejects_over_max() {
let s = "a".repeat(SYNQRO_MAX_INPUT_LEN + 1);
let err = validate_input(&s).unwrap_err();
assert!(matches!(err, SynqroError::InvalidInput(_)));
}
#[test]
fn next_error_id_is_monotonic() {
let a = next_error_id();
let b = next_error_id();
assert!(b > a);
}
#[test]
fn synqro_result_ok_has_null_message() {
let r = SynqroResult::ok();
assert_eq!(r.status, SynqroStatus::SynqroOk);
assert!(r.message.is_null());
assert_eq!(r.error_id, 0);
}
#[test]
fn synqro_result_err_has_non_null_message() {
let r = SynqroResult::err(SynqroStatus::SynqroErrCrypto, "bad key", 42);
assert_eq!(r.status, SynqroStatus::SynqroErrCrypto);
assert!(!r.message.is_null());
let _ = unsafe { std::ffi::CString::from_raw(r.message as *mut c_char) };
}
#[test]
fn internal_error_message_is_redacted() {
let err = SynqroError::Internal("super secret details".into());
let r = SynqroResult::from_error(&err);
assert_eq!(r.status, SynqroStatus::SynqroErrInternal);
assert!(!r.message.is_null());
let msg = unsafe { std::ffi::CStr::from_ptr(r.message) }
.to_string_lossy()
.into_owned();
assert!(
!msg.contains("super secret details"),
"internal detail leaked: {}",
msg
);
let _ = unsafe { std::ffi::CString::from_raw(r.message as *mut c_char) };
}
#[test]
fn status_mapping_is_correct() {
let cases: Vec<(SynqroError, SynqroStatus)> = vec![
(
SynqroError::InvalidInput("x".into()),
SynqroStatus::SynqroErrInvalidInput,
),
(
SynqroError::Crypto("x".into()),
SynqroStatus::SynqroErrCrypto,
),
(
SynqroError::Network("x".into()),
SynqroStatus::SynqroErrNetwork,
),
(
SynqroError::SignatureVerification("x".into()),
SynqroStatus::SynqroErrSignature,
),
(
SynqroError::RollbackBlocked("x".into()),
SynqroStatus::SynqroErrRollback,
),
(
SynqroError::Permission("x".into()),
SynqroStatus::SynqroErrPermission,
),
(
SynqroError::Internal("x".into()),
SynqroStatus::SynqroErrInternal,
),
(
SynqroError::Signature("x".into()),
SynqroStatus::SynqroErrSignature,
),
(
SynqroError::Rollback("x".into()),
SynqroStatus::SynqroErrRollback,
),
(
SynqroError::Keychain("x".into()),
SynqroStatus::SynqroErrPermission,
),
(SynqroError::Degraded, SynqroStatus::SynqroErrInternal),
];
for (err, expected) in cases {
assert_eq!(SynqroStatus::from(&err), expected, "failed for {:?}", err);
}
}
}