use crate::errors::Error;
use crate::ffi::Slice;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Status {
Ok = 0,
Closed = 1,
Protocol = 2,
Limit = 3,
Stream = 4,
Timeout = 5,
TLS = 6,
Version = 7,
IO = 8,
Invalid = 9,
Runtime = 10,
}
impl Status {
pub fn from_code(code: i32) -> Option<Self> {
Some(match code {
0 => Self::Ok,
1 => Self::Closed,
2 => Self::Protocol,
3 => Self::Limit,
4 => Self::Stream,
5 => Self::Timeout,
6 => Self::TLS,
7 => Self::Version,
8 => Self::IO,
9 => Self::Invalid,
10 => Self::Runtime,
_ => return None,
})
}
pub fn of(error: &Error) -> Self {
match error {
Error::Closed => Self::Closed,
Error::Protocol(_) => Self::Protocol,
Error::Limit(_) => Self::Limit,
Error::Stream { .. } => Self::Stream,
Error::Timeout(_) => Self::Timeout,
Error::TLS(_) => Self::TLS,
Error::Version(_) => Self::Version,
Error::IO(_) => Self::IO,
}
}
pub fn message(&self) -> &'static str {
match self {
Self::Ok => "ok",
Self::Closed => "connection closed",
Self::Protocol => "protocol violation",
Self::Limit => "limit exceeded",
Self::Stream => "stream failed",
Self::Timeout => "timed out",
Self::TLS => "tls error",
Self::Version => "version negotiation failed",
Self::IO => "io error",
Self::Invalid => "invalid argument",
Self::Runtime => "runtime unavailable",
}
}
}
pub struct ErrorHandle {
pub status: Status,
pub message: String,
pub reason: String,
pub stream_id: Option<crate::models::StreamID>,
pub code: Option<u64>,
}
impl ErrorHandle {
pub fn new(error: &Error) -> Self {
let (stream_id, code) = match error {
Error::Stream { id, code, .. } => (Some(*id), Some(*code)),
_ => (None, None),
};
Self { status: Status::of(error), message: error.to_string(), reason: Self::reason(error), stream_id, code }
}
pub fn reason(error: &Error) -> String {
match error {
Error::Closed => String::new(),
Error::Protocol(reason) | Error::Limit(reason) | Error::Timeout(reason) | Error::TLS(reason) | Error::Version(reason) => reason.clone(),
Error::Stream { reason, .. } => reason.clone(),
Error::IO(failure) => failure.to_string(),
}
}
pub fn build(status: Status, reason: String) -> Error {
match status {
Status::Ok | Status::Closed => Error::Closed,
Status::Protocol | Status::Invalid | Status::Runtime => Error::Protocol(reason),
Status::Limit => Error::Limit(reason),
Status::Stream => Error::stream(crate::models::StreamID(0), 0, reason),
Status::Timeout => Error::Timeout(reason),
Status::TLS => Error::TLS(reason),
Status::Version => Error::Version(reason),
Status::IO => Error::IO(std::io::Error::other(reason)),
}
}
pub unsafe fn report(out: *mut *mut ErrorHandle, error: &Error) -> Status {
let status = Status::of(error);
if !out.is_null() {
unsafe { *out = Box::into_raw(Box::new(Self::new(error))) };
}
status
}
pub unsafe fn raise(out: *mut *mut ErrorHandle, status: Status) -> Status {
if !out.is_null() {
let handle = Self { status, message: status.message().to_owned(), reason: status.message().to_owned(), stream_id: None, code: None };
unsafe { *out = Box::into_raw(Box::new(handle)) };
}
status
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_free(error: *mut ErrorHandle) {
if !error.is_null() {
drop(unsafe { Box::from_raw(error) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_status(error: *const ErrorHandle) -> Status {
match unsafe { error.as_ref() } {
Some(error) => error.status,
None => Status::Invalid,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_message(error: *const ErrorHandle) -> Slice {
match unsafe { error.as_ref() } {
Some(error) => Slice::text(&error.message),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_stream_id(error: *const ErrorHandle) -> i64 {
match unsafe { error.as_ref() }.and_then(|error| error.stream_id) {
Some(stream_id) => stream_id.0 as i64,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_code(error: *const ErrorHandle) -> i64 {
match unsafe { error.as_ref() }.and_then(|error| error.code) {
Some(code) => code as i64,
None => -1,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_status_message(status: i32) -> Slice {
Slice::maybe(Status::from_code(status).map(|status| status.message()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_new(status: i32, reason: *const u8, reason_len: usize) -> *mut ErrorHandle {
let Some(status) = Status::from_code(status) else {
return std::ptr::null_mut();
};
let reason = unsafe { Slice::borrow_text(reason, reason_len) }.unwrap_or_default().to_owned();
Box::into_raw(Box::new(ErrorHandle::new(&ErrorHandle::build(status, reason))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_stream(stream_id: u64, code: u64, reason: *const u8, reason_len: usize) -> *mut ErrorHandle {
let reason = unsafe { Slice::borrow_text(reason, reason_len) }.unwrap_or_default();
Box::into_raw(Box::new(ErrorHandle::new(&Error::stream(crate::models::StreamID(stream_id), code, reason))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_on_stream(error: *mut ErrorHandle, stream_id: u64, code: u64) -> *mut ErrorHandle {
if error.is_null() {
return std::ptr::null_mut();
}
let handle = *unsafe { Box::from_raw(error) };
let narrowed = ErrorHandle::build(handle.status, handle.reason).on_stream(crate::models::StreamID(stream_id), code);
Box::into_raw(Box::new(ErrorHandle::new(&narrowed)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_reason(error: *const ErrorHandle) -> Slice {
match unsafe { error.as_ref() } {
Some(error) => Slice::text(&error.reason),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_tls(reason: *const u8, reason_len: usize) -> *mut ErrorHandle {
let reason = unsafe { Slice::borrow_text(reason, reason_len) }.unwrap_or_default();
Box::into_raw(Box::new(ErrorHandle::new(&Error::tls(reason))))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_error_quic(reason: *const u8, reason_len: usize) -> *mut ErrorHandle {
let reason = unsafe { Slice::borrow_text(reason, reason_len) }.unwrap_or_default();
Box::into_raw(Box::new(ErrorHandle::new(&Error::quic(reason))))
}