soyokaze 0.6.3

HTTP/1/2/3 Library Crate
Documentation
//! A C ABI over the crate, for callers outside Rust.
//!
//! Every symbol here is `extern "C"` and prefixed `soyokaze_`, and the shared
//! library the crate builds as (`libsoyokaze.so`, `libsoyokaze.dylib`,
//! `soyokaze.dll`) exports exactly this surface. The C declarations live in
//! `include/soyokaze.h`.
//!
//! # Layout
//!
//! The modules mirror the crate they wrap: [`models`] carries [`URL`] and
//! [`Message`], [`errors`] carries [`Error`] across the boundary, [`cookies`]
//! carries the cookie types, [`hsts`] the HSTS policy,
//! [`responses`] the response constructors,
//! [`finalizer`] the date formatting, [`websocket`] the WebSocket connection,
//! [`tls`] the identities and Encrypted Client Hello, [`api`] the two entry
//! points, and [`helpers`] the codecs. What every module shares lives here —
//! the [`Slice`] and [`Buffer`] octet views, and the [`Runtime`] that turns
//! the crate's async surface into blocking calls.
//!
//! [`Error`]: crate::errors::Error
//! [`URL`]: crate::models::URL
//! [`Message`]: crate::models::Message
//!
//! # Conventions
//!
//! - A fallible call returns a [`Status`] and writes its result through an out
//!   parameter. Passing a non-null `error` out parameter takes ownership of an
//!   [`Error`] handle describing the failure, which the caller frees with
//!   `soyokaze_error_free`.
//! - Text and octets go in as a pointer and a length, never as a NUL-terminated
//!   string, so a value may hold a NUL and need not be copied to be passed.
//! - Text and octets come back either as a [`Slice`], borrowed from a handle and
//!   valid until that handle is freed or modified, or as a [`Buffer`], owned by
//!   the caller and freed with `soyokaze_buffer_free`.
//! - A handle is freed exactly once, with the `_free` call that matches the
//!   `_new`, `_parse` or `_request` call that produced it. A call documented as
//!   consuming a handle frees it itself, and the caller must not.
//! - A null handle is treated as absent wherever that is meaningful, and is
//!   never dereferenced.

pub mod models;
pub mod errors;
pub mod cookies;
pub mod hsts;
pub mod responses;
pub mod finalizer;
pub mod websocket;
pub mod tls;

pub mod api {
    //! The two entry points a caller of the library reaches for first, from C.
    //!
    //! [`client`] dials an origin, [`server`] binds ports and accepts
    //! connections, and [`common`] holds what the two configure in common.
    //! [`gate`] is the server's admission control, and [`cluster`] runs it
    //! across worker threads.
    //!
    //! Each module wraps its namesake in [`crate::api`].

    pub mod common;
    pub mod client;
    pub mod server;
    pub mod gate;
    pub mod cluster;
}

pub mod protocol {
    //! One connection type per HTTP version, over a shared vocabulary, from C.
    //!
    //! Each module wraps its namesake in [`crate::protocol`]. What crosses is
    //! the wire format each version is written in — frames, field sections,
    //! start lines, chunks — rather than the connections themselves, which are
    //! reached through [`crate::ffi::api`] as one kind of handle whichever
    //! version framed them.

    pub mod common;
    pub mod quic;
    pub mod h1;
    pub mod h2;
    pub mod h3;
}

pub mod helpers {
    //! The codecs and utilities the protocol implementations share, from C.
    //!
    //! Each module wraps its namesake in [`crate::helpers`].

    pub mod base64;
    pub mod scan;
    pub mod text;
    pub mod sha1;
    pub mod sync;
    pub mod huffman;
    pub mod fields;
    pub mod hpack;
    pub mod qpack;
    pub mod compression;
}

pub use errors::Status;

/// A borrowed view of octets.
///
/// Points into whichever handle produced it and stays valid until that handle
/// is freed or modified. A `data` of null means the value was absent, which is
/// how a lookup that found nothing is told apart from one that found an empty
/// value.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Slice {
    /// The first octet, or null when the value is absent.
    pub data: *const u8,
    /// How many octets there are.
    pub len: usize,
}

impl Slice {
    /// The slice that stands for an absent value.
    pub const ABSENT: Self = Self { data: std::ptr::null(), len: 0 };

    /// A view of `octets`.
    pub fn new(octets: &[u8]) -> Self {
        Self { data: octets.as_ptr(), len: octets.len() }
    }

    /// A view of `text`.
    pub fn text(text: &str) -> Self {
        Self::new(text.as_bytes())
    }

    /// A view of `text`, or [`Slice::ABSENT`] when there is none.
    pub fn maybe(text: Option<&str>) -> Self {
        match text {
            Some(text) => Self::text(text),
            None => Self::ABSENT,
        }
    }

    /// Whether the value was absent.
    pub fn is_absent(&self) -> bool {
        self.data.is_null()
    }

    /// Borrows `len` octets from `data`.
    ///
    /// A null `data` borrows nothing, which is how an absent argument is
    /// passed.
    ///
    /// # Safety
    ///
    /// `data` must either be null or point to `len` readable octets that
    /// outlive the returned slice.
    pub unsafe fn borrow<'a>(data: *const u8, len: usize) -> Option<&'a [u8]> {
        if data.is_null() {
            return None;
        }

        Some(unsafe { std::slice::from_raw_parts(data, len) })
    }

    /// Borrows `len` octets from `data` as UTF-8.
    ///
    /// Returns `None` when the argument is absent or is not UTF-8, which
    /// callers report as [`Status::Invalid`].
    ///
    /// # Safety
    ///
    /// As [`Slice::borrow`].
    pub unsafe fn borrow_text<'a>(data: *const u8, len: usize) -> Option<&'a str> {
        std::str::from_utf8(unsafe { Self::borrow(data, len) }?).ok()
    }
}

/// Octets owned by the caller.
///
/// Freed with [`soyokaze_buffer_free`]. `capacity` is what the allocation was
/// made with and has to be handed back untouched for the memory to be released.
#[repr(C)]
pub struct Buffer {
    /// The first octet, or null when the buffer is empty.
    pub data: *mut u8,
    /// How many octets there are.
    pub len: usize,
    /// How many octets were allocated.
    pub capacity: usize,
}

impl Buffer {
    /// An empty buffer, which needs no freeing but may be freed anyway.
    pub const EMPTY: Self = Self { data: std::ptr::null_mut(), len: 0, capacity: 0 };

    /// Hands `octets` to the caller.
    ///
    /// An empty `octets` comes back as [`Buffer::EMPTY`] rather than as a
    /// pointer to nothing. A `Vec` that holds no octets still carries a
    /// pointer — the aligned one it stands on before it allocates — and
    /// handing that back would make `data` non-null for a buffer that is
    /// empty, which is exactly what this type documents null to mean.
    pub fn new(octets: Vec<u8>) -> Self {
        if octets.is_empty() {
            return Self::EMPTY;
        }

        let mut octets = std::mem::ManuallyDrop::new(octets);
        Self { data: octets.as_mut_ptr(), len: octets.len(), capacity: octets.capacity() }
    }
}

/// Releases a [`Buffer`].
///
/// A buffer that was already freed, or that never held anything, must not be
/// passed twice; an empty buffer is safe to pass.
///
/// # Safety
///
/// `buffer` must be one this library produced and has not yet been freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_buffer_free(buffer: Buffer) {
    if !buffer.data.is_null() {
        drop(unsafe { Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity) });
    }
}

/// The crate's version, as `MAJOR.MINOR.PATCH`.
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_version() -> Slice {
    Slice::text(env!("CARGO_PKG_VERSION"))
}

/// A raw pointer wrapped so a spawned task may carry it.
///
/// The C caller already promised, by handing the pointer to a server that
/// reaches it from many threads, that this is sound.
pub struct SendPtr<T: ?Sized>(pub *mut T);

unsafe impl<T: ?Sized> Send for SendPtr<T> {}

/// The runtime the blocking calls in this module drive.
///
/// The crate's own surface is async; every FFI call that has to wait runs on
/// one of these. It is multi-threaded, so work a call leaves running — the
/// accept loops [`api::server::soyokaze_server_serve`] starts, most of all —
/// keeps running after that call returns.
pub struct Runtime(pub tokio::runtime::Runtime);

/// Builds a [`Runtime`] with `workers` threads, or one thread per core when
/// `workers` is zero.
///
/// Returns null when the runtime cannot be built.
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_runtime_new(workers: u32) -> *mut Runtime {
    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all();

    if workers > 0 {
        builder.worker_threads(workers as usize);
    }

    match builder.build() {
        Ok(runtime) => Box::into_raw(Box::new(Runtime(runtime))),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Releases a [`Runtime`], waiting for the work still on it to finish.
///
/// # Safety
///
/// `runtime` must come from [`soyokaze_runtime_new`] and not have been freed.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_runtime_free(runtime: *mut Runtime) {
    if !runtime.is_null() {
        drop(unsafe { Box::from_raw(runtime) });
    }
}