use bytes::Bytes;
use crate::errors::Error;
use crate::ffi::errors::{ErrorHandle, Status};
use crate::ffi::{Buffer, Runtime, Slice};
use crate::models::{Body, Message, Method, Role, URL, Version};
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PortKind {
UDS = 0,
TCP = 1,
QUIC = 2,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Port {
pub kind: PortKind,
pub number: u16,
pub path: *const u8,
pub path_len: usize,
}
impl Port {
pub unsafe fn parse(&self) -> Option<crate::models::Port> {
match self.kind {
PortKind::TCP => Some(crate::models::Port::TCP(self.number)),
PortKind::QUIC => Some(crate::models::Port::QUIC(self.number)),
PortKind::UDS => Some(crate::models::Port::UDS(unsafe { Slice::borrow_text(self.path, self.path_len) }?.to_owned())),
}
}
pub unsafe fn parse_all(ports: *const Port, count: usize) -> Option<Vec<crate::models::Port>> {
if ports.is_null() {
return None;
}
let mut parsed = Vec::with_capacity(count);
for index in 0..count {
parsed.push(unsafe { (*ports.add(index)).parse() }?);
}
Some(parsed)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_parse(url: *const u8, url_len: usize, out: *mut *mut URL, error: *mut *mut ErrorHandle) -> Status {
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let Some(text) = (unsafe { Slice::borrow_text(url, url_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match URL::parse(text) {
Ok(url) => {
unsafe { *out = Box::into_raw(Box::new(url)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_free(url: *mut URL) {
if !url.is_null() {
drop(unsafe { Box::from_raw(url) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_scheme(url: *const URL) -> Slice {
Slice::maybe(unsafe { url.as_ref() }.map(|url| url.scheme.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_host(url: *const URL) -> Slice {
Slice::maybe(unsafe { url.as_ref() }.map(|url| url.host.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_target(url: *const URL) -> Slice {
Slice::maybe(unsafe { url.as_ref() }.map(|url| url.target.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_port(url: *const URL) -> u16 {
unsafe { url.as_ref() }.map_or(0, |url| url.port)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_secure(url: *const URL) -> bool {
unsafe { url.as_ref() }.is_some_and(|url| url.secure())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_url_authority(url: *const URL) -> Buffer {
match unsafe { url.as_ref() } {
Some(url) => Buffer::new(url.authority().into_bytes()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_message_new(version: Version) -> *mut Message {
Box::into_raw(Box::new(Message::new(version)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_request(method: Method, target: *const u8, target_len: usize, version: Version) -> *mut Message {
match unsafe { Slice::borrow_text(target, target_len) } {
Some(target) => Box::into_raw(Box::new(Message::request(method, target, version))),
None => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_message_response(status_code: u16, version: Version) -> *mut Message {
Box::into_raw(Box::new(Message::response(status_code, version)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_free(message: *mut Message) {
if !message.is_null() {
drop(unsafe { Box::from_raw(message) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_version(message: *const Message) -> Version {
unsafe { message.as_ref() }.map_or(Version::V1_1, |message| message.version)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_method(message: *const Message) -> i32 {
match unsafe { message.as_ref() }.and_then(|message| message.method) {
Some(method) => method as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_status_code(message: *const Message) -> i32 {
match unsafe { message.as_ref() }.and_then(|message| message.status_code) {
Some(status_code) => status_code as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_target(message: *const Message) -> Slice {
Slice::maybe(unsafe { message.as_ref() }.and_then(|message| message.target.as_deref()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_is_request(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.is_request())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_is_response(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.is_response())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_is_informational(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.is_informational())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_secure(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.security.secure)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_header_count(message: *const Message) -> usize {
unsafe { message.as_ref() }.and_then(|message| message.headers.as_ref()).map_or(0, |headers| headers.len())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_header_name(message: *const Message, index: usize) -> Slice {
let field = unsafe { message.as_ref() }.and_then(|message| message.headers.as_ref()).and_then(|headers| headers.iter().nth(index)).map(|(name, _)| name);
Slice::maybe(field)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_header_value(message: *const Message, index: usize) -> Slice {
let field = unsafe { message.as_ref() }.and_then(|message| message.headers.as_ref()).and_then(|headers| headers.iter().nth(index)).map(|(_, value)| value);
Slice::maybe(field)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_header(message: *const Message, name: *const u8, name_len: usize) -> Slice {
let Some(name) = (unsafe { Slice::borrow_text(name, name_len) }) else {
return Slice::ABSENT;
};
Slice::maybe(unsafe { message.as_ref() }.and_then(|message| message.headers.as_ref()).and_then(|headers| headers.get(name)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_append_header(message: *mut Message, name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> bool {
let (Some(message), Some(name), Some(value)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) })
else {
return false;
};
message.headers.get_or_insert_default().append(name, value);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_insert_header(message: *mut Message, name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> bool {
let (Some(message), Some(name), Some(value)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) })
else {
return false;
};
message.headers.get_or_insert_default().insert(name, value);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_remove_header(message: *mut Message, name: *const u8, name_len: usize) -> bool {
let (Some(message), Some(name)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }) else {
return false;
};
message.headers.as_mut().is_some_and(|headers| headers.remove(name))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_trailer_count(message: *const Message) -> usize {
unsafe { message.as_ref() }.and_then(|message| message.trailers.as_ref()).map_or(0, |trailers| trailers.len())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_trailer_name(message: *const Message, index: usize) -> Slice {
let field = unsafe { message.as_ref() }.and_then(|message| message.trailers.as_ref()).and_then(|trailers| trailers.iter().nth(index)).map(|(name, _)| name);
Slice::maybe(field)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_trailer_value(message: *const Message, index: usize) -> Slice {
let field = unsafe { message.as_ref() }.and_then(|message| message.trailers.as_ref()).and_then(|trailers| trailers.iter().nth(index)).map(|(_, value)| value);
Slice::maybe(field)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_trailer(message: *const Message, name: *const u8, name_len: usize) -> Slice {
let Some(name) = (unsafe { Slice::borrow_text(name, name_len) }) else {
return Slice::ABSENT;
};
Slice::maybe(unsafe { message.as_ref() }.and_then(|message| message.trailers.as_ref()).and_then(|trailers| trailers.get(name)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_append_trailer(message: *mut Message, name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> bool {
let (Some(message), Some(name), Some(value)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) })
else {
return false;
};
message.trailers.get_or_insert_default().append(name, value);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_insert_trailer(message: *mut Message, name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> bool {
let (Some(message), Some(name), Some(value)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) })
else {
return false;
};
message.trailers.get_or_insert_default().insert(name, value);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_remove_trailer(message: *mut Message, name: *const u8, name_len: usize) -> bool {
let (Some(message), Some(name)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }) else {
return false;
};
message.trailers.as_mut().is_some_and(|trailers| trailers.remove(name))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_stream_id(message: *const Message) -> i64 {
match unsafe { message.as_ref() }.and_then(|message| message.stream_id) {
Some(stream_id) => stream_id.0 as i64,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_set_stream_id(message: *mut Message, stream_id: i64) -> bool {
let Some(message) = (unsafe { message.as_mut() }) else {
return false;
};
message.stream_id = u64::try_from(stream_id).ok().map(crate::models::StreamID);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_connection_id(message: *const Message) -> Slice {
match unsafe { message.as_ref() }.and_then(|message| message.connection_id.as_ref()) {
Some(id) => Slice::new(&id.0),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_set_secure(message: *mut Message, secure: bool) -> bool {
let Some(message) = (unsafe { message.as_mut() }) else {
return false;
};
message.security.secure = secure;
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_early_data(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.security.early_data)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_tls(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.security.tls)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_tls_version(message: *const Message) -> i32 {
match unsafe { message.as_ref() }.and_then(|message| message.security.tls_version) {
Some(version) => version.0 as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_tls_group(message: *const Message) -> i32 {
match unsafe { message.as_ref() }.and_then(|message| message.security.tls_group) {
Some(group) => group.0 as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_tls_cipher(message: *const Message) -> i32 {
match unsafe { message.as_ref() }.and_then(|message| message.security.tls_cipher) {
Some(cipher) => cipher.0 as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_quic(message: *const Message) -> bool {
unsafe { message.as_ref() }.is_some_and(|message| message.security.quic)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_quic_version(message: *const Message) -> i64 {
match unsafe { message.as_ref() }.and_then(|message| message.security.quic_version) {
Some(version) => version as i64,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_set_body_data(message: *mut Message, data: *const u8, data_len: usize) -> bool {
let (Some(message), Some(data)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow(data, data_len) }) else {
return false;
};
message.body = Some(Body::Data(Bytes::copy_from_slice(data)));
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_set_body_text(message: *mut Message, text: *const u8, text_len: usize) -> bool {
let (Some(message), Some(text)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(text, text_len) }) else {
return false;
};
message.body = Some(Body::Text(text.to_owned()));
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_set_body_file(message: *mut Message, path: *const u8, path_len: usize) -> bool {
let (Some(message), Some(path)) = (unsafe { message.as_mut() }, unsafe { Slice::borrow_text(path, path_len) }) else {
return false;
};
message.body = Some(Body::File(path.to_owned()));
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_body_len(message: *const Message) -> i64 {
match unsafe { message.as_ref() }.and_then(|message| message.body.as_ref()).and_then(|body| body.len()) {
Some(len) => len as i64,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_message_body(runtime: *mut Runtime, message: *const Message, out: *mut Buffer, error: *mut *mut ErrorHandle) -> Status {
let (Some(runtime), Some(message)) = (unsafe { runtime.as_ref() }, unsafe { message.as_ref() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let Some(body) = message.body.as_ref() else {
unsafe { *out = Buffer::EMPTY };
return Status::Ok;
};
match runtime.0.block_on(body.bytes()) {
Ok(octets) => {
unsafe { *out = Buffer::new(octets.to_vec()) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &Error::IO(failure)) },
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Limits {
pub max_message_size: u64,
pub max_message_body_size: u64,
pub max_startline_size: u32,
pub max_headers_size: u64,
pub max_header_count: u16,
pub max_chunk_header_size: u32,
pub read_chunk_size: u64,
pub idle_capacity: u64,
pub max_pending_handshakes: u32,
pub read_timeout: f64,
pub write_timeout: f64,
pub receive_timeout: f64,
pub send_timeout: f64,
pub inline_body_size: u64,
pub max_concurrent_streams: u32,
pub max_connection_buffer_size: u64,
pub max_premature_resets: u32,
pub max_encoder_table_size: u64,
pub max_idle_frames: u32,
pub output_high_water: u64,
pub max_requests_per_connection: u64,
pub qpack_block_timeout: f64,
pub max_peer_uni_streams: u32,
pub max_outstanding_sections: u32,
pub max_blocked_streams: u32,
pub tunnel_backlog: u32,
pub command_backlog: u32,
pub ws_linger_timeout: f64,
pub ws_max_fragments: u16,
pub max_cookies: u32,
pub max_cookies_per_domain: u16,
pub max_hsts_entries: u32,
}
impl Limits {
pub fn parse(&self) -> crate::models::Limits {
crate::models::Limits {
max_message_size: self.max_message_size,
max_message_body_size: self.max_message_body_size,
max_startline_size: self.max_startline_size,
max_headers_size: self.max_headers_size,
max_header_count: self.max_header_count,
max_chunk_header_size: self.max_chunk_header_size,
read_chunk_size: self.read_chunk_size,
idle_capacity: self.idle_capacity,
max_pending_handshakes: self.max_pending_handshakes,
read_timeout: self.read_timeout,
write_timeout: self.write_timeout,
receive_timeout: self.receive_timeout,
send_timeout: self.send_timeout,
inline_body_size: self.inline_body_size,
max_concurrent_streams: self.max_concurrent_streams,
max_connection_buffer_size: self.max_connection_buffer_size,
max_premature_resets: self.max_premature_resets,
max_encoder_table_size: self.max_encoder_table_size,
max_idle_frames: self.max_idle_frames,
output_high_water: self.output_high_water,
max_requests_per_connection: self.max_requests_per_connection,
qpack_block_timeout: self.qpack_block_timeout,
max_peer_uni_streams: self.max_peer_uni_streams,
max_outstanding_sections: self.max_outstanding_sections,
max_blocked_streams: self.max_blocked_streams,
tunnel_backlog: self.tunnel_backlog,
command_backlog: self.command_backlog,
ws_linger_timeout: self.ws_linger_timeout,
ws_max_fragments: self.ws_max_fragments,
max_cookies: self.max_cookies,
max_cookies_per_domain: self.max_cookies_per_domain,
max_hsts_entries: self.max_hsts_entries,
}
}
pub fn build(limits: &crate::models::Limits) -> Self {
Self {
max_message_size: limits.max_message_size,
max_message_body_size: limits.max_message_body_size,
max_startline_size: limits.max_startline_size,
max_headers_size: limits.max_headers_size,
max_header_count: limits.max_header_count,
max_chunk_header_size: limits.max_chunk_header_size,
read_chunk_size: limits.read_chunk_size,
idle_capacity: limits.idle_capacity,
max_pending_handshakes: limits.max_pending_handshakes,
read_timeout: limits.read_timeout,
write_timeout: limits.write_timeout,
receive_timeout: limits.receive_timeout,
send_timeout: limits.send_timeout,
inline_body_size: limits.inline_body_size,
max_concurrent_streams: limits.max_concurrent_streams,
max_connection_buffer_size: limits.max_connection_buffer_size,
max_premature_resets: limits.max_premature_resets,
max_encoder_table_size: limits.max_encoder_table_size,
max_idle_frames: limits.max_idle_frames,
output_high_water: limits.output_high_water,
max_requests_per_connection: limits.max_requests_per_connection,
qpack_block_timeout: limits.qpack_block_timeout,
max_peer_uni_streams: limits.max_peer_uni_streams,
max_outstanding_sections: limits.max_outstanding_sections,
max_blocked_streams: limits.max_blocked_streams,
tunnel_backlog: limits.tunnel_backlog,
command_backlog: limits.command_backlog,
ws_linger_timeout: limits.ws_linger_timeout,
ws_max_fragments: limits.ws_max_fragments,
max_cookies: limits.max_cookies,
max_cookies_per_domain: limits.max_cookies_per_domain,
max_hsts_entries: limits.max_hsts_entries,
}
}
pub unsafe fn or_default(limits: *const Limits) -> crate::models::Limits {
match unsafe { limits.as_ref() } {
Some(limits) => limits.parse(),
None => crate::models::Limits::default(),
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_limits_default() -> Limits {
Limits::build(&crate::models::Limits::default())
}
impl Version {
pub unsafe fn parse_all(versions: *const i32, count: usize) -> Option<Vec<Version>> {
if versions.is_null() {
return Some(Vec::new());
}
let mut parsed = Vec::with_capacity(count);
for index in 0..count {
parsed.push(match unsafe { *versions.add(index) } {
0 => Version::V1_0,
1 => Version::V1_1,
2 => Version::V2_0,
3 => Version::V3_0,
_ => return None,
});
}
Some(parsed)
}
}
impl Role {
pub fn build(role: Role) -> u32 {
match role {
Role::UserAgent => 0,
Role::Origin => 1,
Role::Proxy => 2,
Role::Gateway => 3,
Role::Tunnel => 4,
}
}
}