use bytes::Bytes;
use crate::ffi::errors::{ErrorHandle, Status};
use crate::ffi::{Buffer, Slice};
use crate::models::StreamID;
use crate::protocol::h2::frames::{Code, Flag, Frame, FrameHeader, FrameType, Settings};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct H2Limits {
pub max_message_size: u64,
pub max_message_body_size: u64,
pub max_decompressed_body_size: u64,
pub max_headers_size: u64,
pub max_header_count: u16,
pub max_concurrent_streams: u32,
pub max_connection_buffer_size: u64,
pub max_premature_resets: u32,
pub max_idle_frames: u32,
pub output_high_water: u64,
pub max_encoder_table_size: u64,
pub read_chunk_size: u64,
pub idle_capacity: u64,
pub read_timeout: f64,
pub write_timeout: f64,
pub receive_timeout: f64,
pub send_timeout: f64,
}
impl H2Limits {
pub fn build(limits: &crate::protocol::h2::H2Limits) -> Self {
Self {
max_message_size: limits.max_message_size,
max_message_body_size: limits.max_message_body_size,
max_decompressed_body_size: limits.max_decompressed_body_size,
max_headers_size: limits.max_headers_size,
max_header_count: limits.max_header_count,
max_concurrent_streams: limits.max_concurrent_streams,
max_connection_buffer_size: limits.max_connection_buffer_size,
max_premature_resets: limits.max_premature_resets,
max_idle_frames: limits.max_idle_frames,
output_high_water: limits.output_high_water,
max_encoder_table_size: limits.max_encoder_table_size,
read_chunk_size: limits.read_chunk_size,
idle_capacity: limits.idle_capacity,
read_timeout: limits.read_timeout,
write_timeout: limits.write_timeout,
receive_timeout: limits.receive_timeout,
send_timeout: limits.send_timeout,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_limits_default() -> H2Limits {
H2Limits::build(&crate::protocol::h2::H2Limits::default())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_limits_of(limits: *const crate::ffi::models::Limits) -> H2Limits {
H2Limits::build(&crate::protocol::h2::H2Limits::from(unsafe { crate::ffi::models::Limits::or_default(limits) }))
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_preface() -> Slice {
Slice::new(crate::protocol::h2::PREFACE)
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ErrorCode {
NoError = 0x0,
ProtocolError = 0x1,
InternalError = 0x2,
FlowControlError = 0x3,
SettingsTimeout = 0x4,
StreamClosed = 0x5,
FrameSizeError = 0x6,
RefusedStream = 0x7,
Cancel = 0x8,
CompressionError = 0x9,
ConnectError = 0xa,
EnhanceYourCalm = 0xb,
InadequateSecurity = 0xc,
HTTP11Required = 0xd,
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_flag_end_stream() -> u8 {
Flag::END_STREAM
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_flag_ack() -> u8 {
Flag::ACK
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_flag_end_headers() -> u8 {
Flag::END_HEADERS
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_flag_padded() -> u8 {
Flag::PADDED
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_flag_priority() -> u8 {
Flag::PRIORITY
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Kind {
Data = 0x0,
Headers = 0x1,
Priority = 0x2,
RstStream = 0x3,
Settings = 0x4,
PushPromise = 0x5,
Ping = 0x6,
GoAway = 0x7,
WindowUpdate = 0x8,
Continuation = 0x9,
}
impl Kind {
pub fn build(kind: FrameType) -> Self {
match kind {
FrameType::Data => Self::Data,
FrameType::Headers => Self::Headers,
FrameType::Priority => Self::Priority,
FrameType::RstStream => Self::RstStream,
FrameType::Settings => Self::Settings,
FrameType::PushPromise => Self::PushPromise,
FrameType::Ping => Self::Ping,
FrameType::GoAway => Self::GoAway,
FrameType::WindowUpdate => Self::WindowUpdate,
FrameType::Continuation => Self::Continuation,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_frame_type_known(code: u8) -> bool {
FrameType::from_code(code).is_some()
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_frame_type_streamed(kind: Kind) -> i32 {
let kind = match FrameType::from_code(kind as u8) {
Some(kind) => kind,
None => return -1,
};
match kind.streamed() {
Some(true) => 1,
Some(false) => 0,
None => -1,
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Header {
pub length: u32,
pub kind: Kind,
pub flags: u8,
pub stream_id: u64,
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_header_size() -> usize {
FrameHeader::SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_header_encode(header: Header) -> Buffer {
let header = FrameHeader {
length: header.length,
kind: FrameType::from_code(header.kind as u8).unwrap_or(FrameType::Data),
flags: header.flags,
stream_id: StreamID(header.stream_id),
};
Buffer::new(header.encode().to_vec())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_header_decode(data: *const u8, data_len: usize, out: *mut Header, length: *mut u32) -> bool {
let Some(data) = (unsafe { Slice::borrow(data, data_len) }) else {
return false;
};
let Some(octets) = data.first_chunk::<{ FrameHeader::SIZE }>() else {
return false;
};
let (payload_length, header) = FrameHeader::decode(octets);
if !length.is_null() {
unsafe { *length = payload_length };
}
let Some(header) = header else {
return false;
};
if !out.is_null() {
unsafe {
*out = Header {
length: header.length,
kind: Kind::build(header.kind),
flags: header.flags,
stream_id: header.stream_id.0,
}
};
}
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_data(stream_id: u64, end_stream: bool, data: *const u8, data_len: usize) -> *mut Frame {
let data = Bytes::copy_from_slice(unsafe { Slice::borrow(data, data_len) }.unwrap_or_default());
Box::into_raw(Box::new(Frame::Data { stream_id: StreamID(stream_id), end_stream, data }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_headers(stream_id: u64, end_stream: bool, end_headers: bool, block: *const u8, block_len: usize) -> *mut Frame {
let block = Bytes::copy_from_slice(unsafe { Slice::borrow(block, block_len) }.unwrap_or_default());
Box::into_raw(Box::new(Frame::Headers { stream_id: StreamID(stream_id), end_stream, end_headers, block }))
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_frame_priority(stream_id: u64, dependency: u64, exclusive: bool, weight: u8) -> *mut Frame {
Box::into_raw(Box::new(Frame::Priority { stream_id: StreamID(stream_id), dependency: StreamID(dependency), exclusive, weight }))
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_frame_rst_stream(stream_id: u64, error_code: u32) -> *mut Frame {
Box::into_raw(Box::new(Frame::RstStream { stream_id: StreamID(stream_id), error_code }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_settings(ack: bool, params: *const Parameter, count: usize) -> *mut Frame {
let mut settings = Vec::with_capacity(count);
if !params.is_null() {
for index in 0..count {
let parameter = unsafe { *params.add(index) };
settings.push((parameter.id, parameter.value));
}
}
Box::into_raw(Box::new(Frame::Settings { ack, params: settings }))
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Parameter {
pub id: u16,
pub value: u32,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_push_promise(stream_id: u64, promised_stream_id: u64, block: *const u8, block_len: usize) -> *mut Frame {
let block = Bytes::copy_from_slice(unsafe { Slice::borrow(block, block_len) }.unwrap_or_default());
Box::into_raw(Box::new(Frame::PushPromise { stream_id: StreamID(stream_id), promised_stream_id: StreamID(promised_stream_id), block }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_ping(ack: bool, payload: *const u8) -> *mut Frame {
let payload = match unsafe { Slice::borrow(payload, 8) } {
Some(payload) => match <[u8; 8]>::try_from(payload) {
Ok(payload) => payload,
Err(_) => return std::ptr::null_mut(),
},
None => [0; 8],
};
Box::into_raw(Box::new(Frame::Ping { ack, payload }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_goaway(last_stream_id: u64, error_code: u32, debug_data: *const u8, debug_data_len: usize) -> *mut Frame {
let debug_data = unsafe { Slice::borrow(debug_data, debug_data_len) }.unwrap_or_default().to_vec();
Box::into_raw(Box::new(Frame::GoAway { last_stream_id: StreamID(last_stream_id), error_code, debug_data }))
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_frame_window_update(stream_id: u64, increment: u32) -> *mut Frame {
Box::into_raw(Box::new(Frame::WindowUpdate { stream_id: StreamID(stream_id), increment }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_continuation(stream_id: u64, end_headers: bool, block: *const u8, block_len: usize) -> *mut Frame {
let block = Bytes::copy_from_slice(unsafe { Slice::borrow(block, block_len) }.unwrap_or_default());
Box::into_raw(Box::new(Frame::Continuation { stream_id: StreamID(stream_id), end_headers, block }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_free(frame: *mut Frame) {
if !frame.is_null() {
drop(unsafe { Box::from_raw(frame) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_kind(frame: *const Frame) -> Kind {
match unsafe { frame.as_ref() } {
Some(frame) => Kind::build(frame.kind()),
None => Kind::Data,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_stream_id(frame: *const Frame) -> u64 {
unsafe { frame.as_ref() }.map_or(0, |frame| frame.stream_id().0)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_flags(frame: *const Frame) -> u8 {
unsafe { frame.as_ref() }.map_or(0, |frame| frame.flags())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_bytes(frame: *const Frame) -> Slice {
match unsafe { frame.as_ref() } {
Some(Frame::Data { data, .. }) => Slice::new(data),
Some(Frame::Headers { block, .. }) => Slice::new(block),
Some(Frame::PushPromise { block, .. }) => Slice::new(block),
Some(Frame::Continuation { block, .. }) => Slice::new(block),
Some(Frame::Ping { payload, .. }) => Slice::new(payload),
Some(Frame::GoAway { debug_data, .. }) => Slice::new(debug_data),
_ => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_error_code(frame: *const Frame) -> i64 {
match unsafe { frame.as_ref() } {
Some(Frame::RstStream { error_code, .. }) => *error_code as i64,
Some(Frame::GoAway { error_code, .. }) => *error_code as i64,
_ => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_other_stream_id(frame: *const Frame) -> i64 {
match unsafe { frame.as_ref() } {
Some(Frame::GoAway { last_stream_id, .. }) => last_stream_id.0 as i64,
Some(Frame::PushPromise { promised_stream_id, .. }) => promised_stream_id.0 as i64,
Some(Frame::Priority { dependency, .. }) => dependency.0 as i64,
_ => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_increment(frame: *const Frame) -> i64 {
match unsafe { frame.as_ref() } {
Some(Frame::WindowUpdate { increment, .. }) => *increment as i64,
_ => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_weight(frame: *const Frame) -> i32 {
match unsafe { frame.as_ref() } {
Some(Frame::Priority { weight, .. }) => *weight as i32,
_ => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_exclusive(frame: *const Frame) -> bool {
matches!(unsafe { frame.as_ref() }, Some(Frame::Priority { exclusive: true, .. }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_parameter_count(frame: *const Frame) -> usize {
match unsafe { frame.as_ref() } {
Some(Frame::Settings { params, .. }) => params.len(),
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_parameter(frame: *const Frame, index: usize) -> Parameter {
match unsafe { frame.as_ref() } {
Some(Frame::Settings { params, .. }) => match params.get(index) {
Some(&(id, value)) => Parameter { id, value },
None => Parameter { id: 0, value: 0 },
},
_ => Parameter { id: 0, value: 0 },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_encode(frame: *const Frame) -> Buffer {
match unsafe { frame.as_ref() } {
Some(frame) => Buffer::new(frame.encode()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_payload(frame: *const Frame) -> Buffer {
match unsafe { frame.as_ref() } {
Some(frame) => Buffer::new(frame.payload()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_frame_decode(data: *const u8, data_len: usize, max_frame_size: u32, out: *mut *mut Frame, read: *mut usize, error: *mut *mut ErrorHandle) -> Status {
let Some(data) = (unsafe { Slice::borrow(data, data_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
let mut buffer = bytes::BytesMut::from(data);
let before = buffer.len();
let outcome = Frame::parse(&mut buffer, max_frame_size);
if !read.is_null() {
unsafe { *read = before - buffer.len() };
}
match outcome {
Ok(Some(frame)) => {
if !out.is_null() {
unsafe { *out = Box::into_raw(Box::new(frame)) };
}
Status::Ok
}
Ok(None) => Status::Closed,
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct H2Settings {
pub header_table_size: u32,
pub enable_push: bool,
pub max_concurrent_streams: i64,
pub initial_window_size: u32,
pub max_frame_size: u32,
pub max_header_list_size: i64,
pub enable_connect_protocol: bool,
}
impl H2Settings {
pub fn build(settings: &Settings) -> Self {
Self {
header_table_size: settings.header_table_size,
enable_push: settings.enable_push,
max_concurrent_streams: settings.max_concurrent_streams.map_or(-1, |streams| streams as i64),
initial_window_size: settings.initial_window_size,
max_frame_size: settings.max_frame_size,
max_header_list_size: settings.max_header_list_size.map_or(-1, |size| size as i64),
enable_connect_protocol: settings.enable_connect_protocol,
}
}
pub fn parse(&self) -> Settings {
Settings {
header_table_size: self.header_table_size,
enable_push: self.enable_push,
max_concurrent_streams: u32::try_from(self.max_concurrent_streams).ok(),
initial_window_size: self.initial_window_size,
max_frame_size: self.max_frame_size,
max_header_list_size: u32::try_from(self.max_header_list_size).ok(),
enable_connect_protocol: self.enable_connect_protocol,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_settings_default() -> H2Settings {
H2Settings::build(&Settings::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_settings_peer() -> H2Settings {
H2Settings::build(&Settings::peer())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_settings_parameter_count(settings: *const H2Settings) -> usize {
match unsafe { settings.as_ref() } {
Some(settings) => settings.parse().parameters().len(),
None => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_settings_parameter(settings: *const H2Settings, index: usize) -> Parameter {
let Some(settings) = (unsafe { settings.as_ref() }) else {
return Parameter { id: 0, value: 0 };
};
match settings.parse().parameters().get(index) {
Some(&(id, value)) => Parameter { id, value },
None => Parameter { id: 0, value: 0 },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_h2_settings_apply(settings: *mut H2Settings, id: u16, value: u32, window_delta: *mut i64, error: *mut *mut ErrorHandle) -> Status {
let Some(settings) = (unsafe { settings.as_mut() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
let mut parsed = settings.parse();
match parsed.apply(id, value) {
Ok(delta) => {
*settings = H2Settings::build(&parsed);
if !window_delta.is_null() {
unsafe { *window_delta = delta };
}
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_header_table_size() -> u16 {
Settings::HEADER_TABLE_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_enable_push() -> u16 {
Settings::ENABLE_PUSH
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_max_concurrent_streams() -> u16 {
Settings::MAX_CONCURRENT_STREAMS
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_initial_window_size() -> u16 {
Settings::INITIAL_WINDOW_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_max_frame_size() -> u16 {
Settings::MAX_FRAME_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_max_header_list_size() -> u16 {
Settings::MAX_HEADER_LIST_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_setting_enable_connect_protocol() -> u16 {
Settings::ENABLE_CONNECT_PROTOCOL
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_default_initial_window_size() -> u32 {
Settings::DEFAULT_INITIAL_WINDOW_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_default_max_frame_size() -> u32 {
Settings::DEFAULT_MAX_FRAME_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_maximum_frame_size() -> u32 {
Settings::MAXIMUM_FRAME_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_maximum_window_size() -> u32 {
Settings::MAXIMUM_WINDOW_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_h2_error_code_name(code: u32) -> Slice {
Slice::text(match code {
Code::NO_ERROR => "NO_ERROR",
Code::PROTOCOL_ERROR => "PROTOCOL_ERROR",
Code::INTERNAL_ERROR => "INTERNAL_ERROR",
Code::FLOW_CONTROL_ERROR => "FLOW_CONTROL_ERROR",
Code::SETTINGS_TIMEOUT => "SETTINGS_TIMEOUT",
Code::STREAM_CLOSED => "STREAM_CLOSED",
Code::FRAME_SIZE_ERROR => "FRAME_SIZE_ERROR",
Code::REFUSED_STREAM => "REFUSED_STREAM",
Code::CANCEL => "CANCEL",
Code::COMPRESSION_ERROR => "COMPRESSION_ERROR",
Code::CONNECT_ERROR => "CONNECT_ERROR",
Code::ENHANCE_YOUR_CALM => "ENHANCE_YOUR_CALM",
Code::INADEQUATE_SECURITY => "INADEQUATE_SECURITY",
Code::HTTP_1_1_REQUIRED => "HTTP_1_1_REQUIRED",
_ => "UNKNOWN",
})
}