use crate::api::client::Client;
use crate::ffi::models::Limits;
use crate::ffi::errors::{ErrorHandle, Status};
use crate::ffi::models::Port;
use crate::ffi::tls::{ECHEntry, TLSConfig};
use crate::ffi::websocket::WebSocket;
use crate::ffi::{Buffer, Runtime, Slice};
use crate::models::{Message, Method, Role, URL, Version};
use crate::protocol::base::{AnyConnection, Connection};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ClientLimits {
pub message: Limits,
pub connection_timeout: f64,
}
impl ClientLimits {
pub fn parse(&self) -> crate::api::client::ClientLimits {
crate::api::client::ClientLimits { message: self.message.parse(), connection_timeout: self.connection_timeout }
}
pub fn build(limits: &crate::api::client::ClientLimits) -> Self {
Self { message: Limits::build(&limits.message), connection_timeout: limits.connection_timeout }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_client_limits_default() -> ClientLimits {
ClientLimits::build(&crate::api::client::ClientLimits::default())
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ClientConfig {
pub versions: *const i32,
pub version_count: usize,
pub limits: *const ClientLimits,
pub secure: bool,
pub cookies: bool,
pub hsts: bool,
pub roots: *const Slice,
pub root_count: usize,
pub tls: *const TLSConfig,
pub ech: *const ECHEntry,
pub ech_count: usize,
}
impl ClientConfig {
pub const DEFAULT: Self = Self {
versions: std::ptr::null(),
version_count: 0,
limits: std::ptr::null(),
secure: true,
cookies: true,
hsts: true,
roots: std::ptr::null(),
root_count: 0,
tls: std::ptr::null(),
ech: std::ptr::null(),
ech_count: 0,
};
pub unsafe fn build(&self) -> Option<Client> {
let mut config = crate::api::client::ClientConfig {
secure: self.secure,
cookies: self.cookies,
hsts: self.hsts,
..crate::api::client::ClientConfig::default()
};
let versions = unsafe { Version::parse_all(self.versions, self.version_count) }?;
if !versions.is_empty() {
config.versions = versions;
}
if let Some(limits) = unsafe { self.limits.as_ref() } {
config.limits = limits.parse();
}
if !self.roots.is_null() {
let mut roots = Vec::with_capacity(self.root_count);
for index in 0..self.root_count {
let slice = unsafe { *self.roots.add(index) };
roots.push(unsafe { Slice::borrow(slice.data, slice.len) }?.to_vec());
}
config.roots = Some(roots);
}
if let Some(tls) = unsafe { self.tls.as_ref() } {
config.tls = unsafe { tls.parse() }?;
}
if !self.ech.is_null() {
for index in 0..self.ech_count {
let entry = unsafe { *self.ech.add(index) };
let host = unsafe { Slice::borrow_text(entry.host.data, entry.host.len) }?;
let list = unsafe { Slice::borrow(entry.config_list.data, entry.config_list.len) }?;
config.ech.insert(host.to_owned(), list.to_vec());
}
}
Some(Client::new(config))
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_new(config: *const ClientConfig) -> *mut Client {
let config = unsafe { config.as_ref() }.copied().unwrap_or(ClientConfig::DEFAULT);
match unsafe { config.build() } {
Some(client) => Box::into_raw(Box::new(client)),
None => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_free(client: *mut Client) {
if !client.is_null() {
drop(unsafe { Box::from_raw(client) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_fetch(runtime: *mut Runtime, client: *const Client, method: i32, url: *const u8, url_len: usize, request: *mut Message, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
let request = (!request.is_null()).then(|| *unsafe { Box::from_raw(request) });
let Some(method) = Method::from_code(method) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
let (Some(runtime), Some(client), Some(url)) = (unsafe { runtime.as_ref() }, unsafe { client.as_ref() }, unsafe { Slice::borrow_text(url, url_len) })
else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let (headers, body) = match request {
Some(request) => (request.headers, request.body),
None => (None, None),
};
match runtime.0.block_on(client.fetch(method, url, headers, body)) {
Ok(response) => {
unsafe { *out = Box::into_raw(Box::new(response)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_get(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
unsafe { soyokaze_client_fetch(runtime, client, Method::GET as i32, url, url_len, std::ptr::null_mut(), out, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_head(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
unsafe { soyokaze_client_fetch(runtime, client, Method::HEAD as i32, url, url_len, std::ptr::null_mut(), out, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_post(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, request: *mut Message, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
unsafe { soyokaze_client_fetch(runtime, client, Method::POST as i32, url, url_len, request, out, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_put(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, request: *mut Message, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
unsafe { soyokaze_client_fetch(runtime, client, Method::PUT as i32, url, url_len, request, out, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_delete(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
unsafe { soyokaze_client_fetch(runtime, client, Method::DELETE as i32, url, url_len, std::ptr::null_mut(), out, error) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_open(runtime: *mut Runtime, client: *const Client, url: *const URL, out: *mut *mut AnyConnection, error: *mut *mut ErrorHandle) -> Status {
let (Some(runtime), Some(client), Some(url)) = (unsafe { runtime.as_ref() }, unsafe { client.as_ref() }, unsafe { url.as_ref() })
else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match runtime.0.block_on(client.open(url)) {
Ok(connection) => {
unsafe { *out = Box::into_raw(Box::new(connection)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_connect(runtime: *mut Runtime, client: *const Client, host: *const u8, host_len: usize, port: *const Port, out: *mut *mut AnyConnection, error: *mut *mut ErrorHandle) -> Status {
let (Some(runtime), Some(client), Some(host), Some(port)) = (
unsafe { runtime.as_ref() },
unsafe { client.as_ref() },
unsafe { Slice::borrow_text(host, host_len) },
unsafe { port.as_ref() },
) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
let Some(port) = (unsafe { port.parse() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match runtime.0.block_on(client.connect(host, port)) {
Ok(connection) => {
unsafe { *out = Box::into_raw(Box::new(connection)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_request(runtime: *mut Runtime, client: *const Client, connection: *mut AnyConnection, request: *mut Message, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
if request.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let request = *unsafe { Box::from_raw(request) };
let (Some(runtime), Some(client), Some(connection)) = (unsafe { runtime.as_ref() }, unsafe { client.as_ref() }, unsafe { connection.as_mut() })
else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match runtime.0.block_on(client.request(connection, request)) {
Ok(response) => {
unsafe { *out = Box::into_raw(Box::new(response)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_websocket(runtime: *mut Runtime, client: *const Client, url: *const u8, url_len: usize, out: *mut *mut WebSocket, error: *mut *mut ErrorHandle) -> Status {
let (Some(runtime), Some(client), Some(url)) = (unsafe { runtime.as_ref() }, unsafe { client.as_ref() }, unsafe { Slice::borrow_text(url, url_len) })
else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match runtime.0.block_on(client.websocket(url)) {
Ok(connection) => {
unsafe { *out = Box::into_raw(Box::new(WebSocket { connection, handle: runtime.0.handle().clone() })) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_version(connection: *const AnyConnection) -> Version {
unsafe { connection.as_ref() }.map_or(Version::V1_1, |connection| connection.version())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_role(connection: *const AnyConnection) -> u32 {
match unsafe { connection.as_ref() } {
Some(connection) => Role::build(connection.role()),
None => Role::build(Role::UserAgent),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_id(connection: *const AnyConnection) -> Buffer {
match unsafe { connection.as_ref() } {
Some(connection) => Buffer::new(connection.id().0.to_vec()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_client(connection: *const AnyConnection) -> Buffer {
match unsafe { connection.as_ref() }.and_then(Connection::client) {
Some(client) => Buffer::new(client.to_string().into_bytes()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_send(runtime: *mut Runtime, connection: *mut AnyConnection, message: *mut Message, error: *mut *mut ErrorHandle) -> Status {
if message.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let message = *unsafe { Box::from_raw(message) };
let (Some(runtime), Some(connection)) = (unsafe { runtime.as_ref() }, unsafe { connection.as_mut() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match runtime.0.block_on(connection.send(message)) {
Ok(()) => Status::Ok,
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_receive(runtime: *mut Runtime, connection: *mut AnyConnection, out: *mut *mut Message, error: *mut *mut ErrorHandle) -> Status {
let (Some(runtime), Some(connection)) = (unsafe { runtime.as_ref() }, unsafe { connection.as_mut() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match runtime.0.block_on(connection.receive()) {
Ok(message) => {
unsafe { *out = Box::into_raw(Box::new(message)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_open_websocket(runtime: *mut Runtime, connection: *mut AnyConnection, authority: *const u8, authority_len: usize, target: *const u8, target_len: usize, limits: *const crate::ffi::models::Limits, out: *mut *mut WebSocket, error: *mut *mut ErrorHandle) -> Status {
if connection.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let connection = *unsafe { Box::from_raw(connection) };
let (Some(runtime), Some(authority), Some(target)) = (
unsafe { runtime.as_ref() },
unsafe { Slice::borrow_text(authority, authority_len) },
unsafe { Slice::borrow_text(target, target_len) },
) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let limits = unsafe { crate::ffi::models::Limits::or_default(limits) };
match runtime.0.block_on(connection.open_websocket(authority, target, limits)) {
Ok(socket) => {
unsafe { *out = Box::into_raw(Box::new(WebSocket { connection: socket, handle: runtime.0.handle().clone() })) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_reusable(connection: *const AnyConnection) -> bool {
unsafe { connection.as_ref() }.is_some_and(|connection| connection.reusable())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_close(runtime: *mut Runtime, connection: *mut AnyConnection) {
if let (Some(runtime), Some(connection)) = (unsafe { runtime.as_ref() }, unsafe { connection.as_mut() }) {
runtime.0.block_on(connection.close());
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_connection_free(connection: *mut AnyConnection) {
if !connection.is_null() {
drop(unsafe { Box::from_raw(connection) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_id(client: *const Client, host: *const u8, host_len: usize, target: *const Port) -> Buffer {
let (Some(client), Some(host), Some(target)) = (unsafe { client.as_ref() }, unsafe { Slice::borrow_text(host, host_len) }, unsafe { target.as_ref() }.and_then(|target| unsafe { target.parse() })) else {
return Buffer::EMPTY;
};
Buffer::new(client.id(host, &target).0.to_vec())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_authority(client: *const Client, host: *const u8, host_len: usize, target: *const Port) -> Buffer {
let (Some(client), Some(host), Some(target)) = (unsafe { client.as_ref() }, unsafe { Slice::borrow_text(host, host_len) }, unsafe { target.as_ref() }.and_then(|target| unsafe { target.parse() })) else {
return Buffer::EMPTY;
};
Buffer::new(client.authority(host, &target).into_bytes())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_ech(client: *const Client, host: *const u8, host_len: usize) -> Slice {
let (Some(client), Some(host)) = (unsafe { client.as_ref() }, unsafe { Slice::borrow_text(host, host_len) }) else {
return Slice::ABSENT;
};
match client.ech(host) {
Some(config) => Slice::new(config),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_prior_version(client: *const Client, out: *mut Version, error: *mut *mut ErrorHandle) -> Status {
let Some(client) = (unsafe { client.as_ref() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match client.prior_version() {
Ok(version) => {
if !out.is_null() {
unsafe { *out = version };
}
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_only_quic(client: *const Client) -> bool {
unsafe { client.as_ref() }.is_some_and(|client| client.only_quic())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_version_count(client: *const Client) -> usize {
unsafe { client.as_ref() }.map_or(0, |client| client.config.versions.len())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_version_at(client: *const Client, index: usize) -> i32 {
match unsafe { client.as_ref() }.and_then(|client| client.config.versions.get(index)) {
Some(&version) => version as i32,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_jar(client: *const Client) -> *const crate::cookies::CookieJar {
match unsafe { client.as_ref() }.and_then(|client| client.jar.as_ref()) {
Some(jar) => std::sync::Arc::as_ptr(jar),
None => std::ptr::null(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_store(client: *const Client) -> *const crate::hsts::HSTSStore {
match unsafe { client.as_ref() }.and_then(|client| client.store.as_ref()) {
Some(store) => std::sync::Arc::as_ptr(store),
None => std::ptr::null(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_apply_hsts(client: *const Client, url: *mut URL) -> bool {
let (Some(client), Some(url)) = (unsafe { client.as_ref() }, unsafe { url.as_mut() }) else {
return false;
};
let before = url.scheme.clone();
client.apply_hsts(url, std::time::Instant::now());
before != url.scheme
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_client_request_finalizer(client: *const Client, authority: *const u8, authority_len: usize) -> *mut crate::finalizer::RequestFinalizer {
let (Some(client), Some(authority)) = (unsafe { client.as_ref() }, unsafe { Slice::borrow_text(authority, authority_len) }) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(client.request_finalizer(authority)))
}