diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2d1a6b..a885854 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ If any other issue occurs 1. Make sure you locally checked out the head of the main branch - `git stash --include-untracked && git checkout main && git pull` 2. Make sure all tests pass - - `cargo test` + - `cargo test --workspace --all-features` 3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases) - Only normal releases count, release candidates and draft releases can be ignored 4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...) diff --git a/Cargo.lock b/Cargo.lock index 63e37d3..5b0ce6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1210,9 +1210,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libcrux" @@ -2058,9 +2058,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -2113,18 +2113,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -2154,9 +2154,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ "futures", "log", @@ -2168,9 +2168,9 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", @@ -2339,18 +2339,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bbe7448..df98f3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ doc-comment = "0.3.3" base64ct = { version = "1.6.0", default-features = false } zeroize = "1.8.1" memoffset = "0.9.1" -thiserror = "1.0.68" +thiserror = "1.0.69" paste = "1.0.15" env_logger = "0.10.2" toml = "0.7.8" @@ -50,7 +50,7 @@ log = { version = "0.4.22" } clap = { version = "4.5.20", features = ["derive"] } clap_mangen = "0.2.24" clap_complete = "4.5.37" -serde = { version = "1.0.214", features = ["derive"] } +serde = { version = "1.0.215", features = ["derive"] } arbitrary = { version = "1.4.1", features = ["derive"] } anyhow = { version = "1.0.93", features = ["backtrace", "std"] } mio = { version = "1.0.2", features = ["net", "os-poll"] } @@ -76,7 +76,7 @@ libc = { version = "0.2" } uds = { git = "https://github.com/rosenpass/uds" } #Dev dependencies -serial_test = "3.1.1" +serial_test = "3.2.0" tempfile = "3" stacker = "0.1.17" libfuzzer-sys = "0.4" @@ -89,4 +89,4 @@ procspawn = { version = "1.0.1", features = ["test-support"] } #Broker dependencies (might need cleanup or changes) wireguard-uapi = { version = "3.0.0", features = ["xplatform"] } command-fds = "0.2.3" -rustix = { version = "0.38.39", features = ["net", "fs"] } +rustix = { version = "0.38.40", features = ["net", "fs"] } diff --git a/rosenpass/src/api/mio/connection.rs b/rosenpass/src/api/mio/connection.rs index a3c7a4e..10e4cc2 100644 --- a/rosenpass/src/api/mio/connection.rs +++ b/rosenpass/src/api/mio/connection.rs @@ -88,7 +88,7 @@ impl MioConnection { }) } - pub fn shoud_close(&self) -> bool { + pub fn should_close(&self) -> bool { let exhausted = self .buffers .as_ref() @@ -262,7 +262,7 @@ pub trait MioConnectionContext { } fn should_close(&self) -> bool { - self.mio_connection().shoud_close() + self.mio_connection().should_close() } } diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index 041e672..c3df516 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -43,15 +43,15 @@ pub enum BrokerInterface { #[derive(Parser, Debug)] #[command(author, version, about, long_about, arg_required_else_help = true)] pub struct CliArgs { - /// lowest log level to show – log messages at higher levels will be omitted + /// Lowest log level to show #[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")] log_level: Option, - /// show verbose log output – sets log level to "debug" + /// Show verbose log output – sets log level to "debug" #[arg(short, long, group = "log-level")] verbose: bool, - /// show no log output – sets log level to "error" + /// Show no log output – sets log level to "error" #[arg(short, long, group = "log-level")] quiet: bool, @@ -59,22 +59,23 @@ pub struct CliArgs { #[cfg(feature = "experiment_api")] api: crate::api::cli::ApiCli, - /// path of the wireguard_psk broker socket to connect to + /// Path of the `wireguard_psk` broker socket to connect to #[cfg(feature = "experiment_api")] #[arg(long, group = "psk-broker-specs")] psk_broker_path: Option, - /// fd of the wireguard_spk broker socket to connect to + /// File descriptor of the `wireguard_psk` broker socket to connect to /// - /// when this command is called from another process, the other process can open and bind the - /// Unix socket for the psk broker connection to use themselves, passing it to this process -- - /// in Rust this can be achieved using the - /// [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate + /// When this command is called from another process, the other process can + /// open and bind the Unix socket for the PSK broker connection to use + /// themselves, passing it to this process - in Rust this can be achieved + /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) + /// crate #[cfg(feature = "experiment_api")] #[arg(long, group = "psk-broker-specs")] psk_broker_fd: Option, - /// spawn a psk broker locally using a socket pair + /// Spawn a PSK broker locally using a socket pair #[cfg(feature = "experiment_api")] #[arg(short, long, group = "psk-broker-specs")] psk_broker_spawn: bool, @@ -82,11 +83,16 @@ pub struct CliArgs { #[command(subcommand)] pub command: Option, - /// Generate man page + /// Generate man pages for the CLI + /// + /// This option is used to generate man pages for Rosenpass in the specified + /// directory and exit. #[clap(long, value_name = "out_dir")] pub generate_manpage: Option, /// Generate completion file for a shell + /// + /// This option is used to generate completion files for the specified shell #[clap(long, value_name = "shell")] pub print_completions: Option, } @@ -143,20 +149,20 @@ impl CliArgs { /// represents a command specified via CLI #[derive(Subcommand, Debug)] pub enum CliCommand { - /// Start Rosenpass in server mode and carry on with the key exchange + /// Start Rosenpass key exchanges based on a configuration file /// - /// This will parse the configuration file and perform the key exchange - /// with the specified peers. If a peer's endpoint is specified, this - /// Rosenpass instance will try to initiate a key exchange with the peer, - /// otherwise only initiation attempts from the peer will be responded to. + /// This will parse the configuration file and perform key exchanges with + /// the specified peers. If a peer's endpoint is specified, this Rosenpass + /// instance will try to initiate a key exchange with the peer; otherwise, + /// only initiation attempts from other peers will be responded to. ExchangeConfig { config_file: PathBuf }, - /// Start in daemon mode, performing key exchanges + /// Start Rosenpass key exchanges based on command line arguments /// - /// The configuration is read from the command line. The `peer` token - /// always separates multiple peers, e. g. if the token `peer` appears - /// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments - /// but instead a new peer is created. + /// The configuration is read from the command line. The `peer` token always + /// separates multiple peers, e.g., if the token `peer` appears in the + /// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but + /// instead a new peer is created. /* Explanation: `first_arg` and `rest_of_args` are combined into one * `Vec`. They are only used to trick clap into displaying some * guidance on the CLI usage. @@ -185,7 +191,10 @@ pub enum CliCommand { config_file: Option, }, - /// Generate a demo config file + /// Generate a demo config file for Rosenpass + /// + /// The generated config file will contain a single peer and all common + /// options. GenConfig { config_file: PathBuf, @@ -194,19 +203,19 @@ pub enum CliCommand { force: bool, }, - /// Generate the keys mentioned in a configFile + /// Generate secret & public key for Rosenpass /// - /// Generates secret- & public-key to their destination. If a config file - /// is provided then the key file destination is taken from there. - /// Otherwise the + /// Generates secret & public key to their destination. If a config file is + /// provided then the key file destination is taken from there, otherwise + /// the destination is taken from the CLI arguments. GenKeys { config_file: Option, - /// where to write public-key to + /// Where to write public key to #[clap(short, long)] public_key: Option, - /// where to write secret-key to + /// Where to write secret key to #[clap(short, long)] secret_key: Option, @@ -215,21 +224,27 @@ pub enum CliCommand { force: bool, }, - /// Deprecated - use gen-keys instead + /// Validate a configuration file + /// + /// This command will validate the configuration file and print any errors + /// it finds. If the configuration file is valid, it will print a success. + /// Defined secret & public keys are checked for existence and validity. + Validate { config_files: Vec }, + + /// DEPRECATED - use the gen-keys command instead #[allow(rustdoc::broken_intra_doc_links)] #[allow(rustdoc::invalid_html_tags)] + #[command(hide = true)] Keygen { - // NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"! + // NOTE yes, the legacy keygen argument initially really accepted + // "private-key", not "secret-key"! /// public-key private-key args: Vec, }, - - /// Validate a configuration - Validate { config_files: Vec }, } impl CliArgs { - /// runs the command specified via CLI + /// Runs the command specified via CLI /// /// ## TODO /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... diff --git a/to/src/lib.rs b/to/src/lib.rs index 5cd4ec5..0f52446 100644 --- a/to/src/lib.rs +++ b/to/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(missing_docs)] +#![recursion_limit = "256"] #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] #[cfg(doctest)] diff --git a/to/src/to/beside.rs b/to/src/to/beside.rs index 1a6d5ed..8473dfe 100644 --- a/to/src/to/beside.rs +++ b/to/src/to/beside.rs @@ -5,23 +5,70 @@ use crate::CondenseBeside; pub struct Beside(pub Val, pub Ret); impl Beside { + /// Get an immutable reference to the destination value + /// + /// # Example + /// ``` + /// use rosenpass_to::Beside; + /// + /// let beside = Beside(1, 2); + /// assert_eq!(beside.dest(), &1); + /// ``` pub fn dest(&self) -> &Val { &self.0 } + /// Get an immutable reference to the return value + /// + /// # Example + /// ``` + /// use rosenpass_to::Beside; + /// + /// let beside = Beside(1, 2); + /// assert_eq!(beside.ret(), &2); + /// ``` pub fn ret(&self) -> &Ret { &self.1 } + /// Get a mutable reference to the destination value + /// + /// # Example + /// ``` + /// use rosenpass_to::Beside; + /// + /// let mut beside = Beside(1, 2); + /// *beside.dest_mut() = 3; + /// assert_eq!(beside.dest(), &3); + /// ``` pub fn dest_mut(&mut self) -> &mut Val { &mut self.0 } + /// Get a mutable reference to the return value + /// + /// # Example + /// ``` + /// use rosenpass_to::Beside; + /// + /// let mut beside = Beside(1, 2); + /// *beside.ret_mut() = 3; + /// assert_eq!(beside.ret(), &3); + /// ``` pub fn ret_mut(&mut self) -> &mut Ret { &mut self.1 } /// Perform beside condensation. See [CondenseBeside] + /// + /// # Example + /// ``` + /// use rosenpass_to::Beside; + /// use rosenpass_to::CondenseBeside; + /// + /// let beside = Beside(1, ()); + /// assert_eq!(beside.condense(), 1); + /// ``` pub fn condense(self) -> >::Condensed where Ret: CondenseBeside, diff --git a/to/src/to/condense.rs b/to/src/to/condense.rs index 3ac7b90..b9df4ba 100644 --- a/to/src/to/condense.rs +++ b/to/src/to/condense.rs @@ -7,8 +7,10 @@ /// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the /// condense trait. pub trait CondenseBeside { + /// The type that results from condensation. type Condensed; + /// Takes ownership of `self` and condenses it with the given value. fn condense(self, ret: Val) -> Self::Condensed; } diff --git a/to/src/to/dst_coercion.rs b/to/src/to/dst_coercion.rs index cb9aa3a..fec6db5 100644 --- a/to/src/to/dst_coercion.rs +++ b/to/src/to/dst_coercion.rs @@ -1,6 +1,7 @@ /// Helper performing explicit unsized coercion. /// Used by the [to](crate::to()) function. pub trait DstCoercion { + /// Performs an explicit coercion to the destination type. fn coerce_dest(&mut self) -> &mut Dst; } diff --git a/to/src/to/to_trait.rs b/to/src/to/to_trait.rs index e8fc2bf..63ad177 100644 --- a/to/src/to/to_trait.rs +++ b/to/src/to/to_trait.rs @@ -1,13 +1,16 @@ use crate::{Beside, CondenseBeside}; use std::borrow::BorrowMut; -// The To trait is the core of the to crate; most functions with destinations will either return -// an object that is an instance of this trait or they will return `-> impl To impl To: Sized { + /// Writes self to the destination `out` and returns a value of type `Ret`. + /// + /// This is the core method that must be implemented by all types implementing `To`. fn to(self, out: &mut Dst) -> Ret; /// Generate a destination on the fly with a lambda. diff --git a/to/src/to/with_destination.rs b/to/src/to/with_destination.rs index a08cb5e..6fbec1e 100644 --- a/to/src/to/with_destination.rs +++ b/to/src/to/with_destination.rs @@ -1,20 +1,38 @@ use crate::To; use std::marker::PhantomData; +/// A struct that wraps a closure and implements the `To` trait +/// +/// This allows passing closures that operate on a destination type `Dst` +/// and return `Ret`. +/// +/// # Type Parameters +/// * `Dst` - The destination type the closure operates on +/// * `Ret` - The return type of the closure +/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret` struct ToClosure where Dst: ?Sized, Fun: FnOnce(&mut Dst) -> Ret, { + /// The function to call. fun: Fun, + /// Phantom data to hold the destination type _val: PhantomData>, } +/// Implementation of the `To` trait for ToClosure +/// +/// This enables calling the wrapped closure with a destination reference. impl To for ToClosure where Dst: ?Sized, Fun: FnOnce(&mut Dst) -> Ret, { + /// Execute the wrapped closure with the given destination + /// + /// # Arguments + /// * `out` - Mutable reference to the destination fn to(self, out: &mut Dst) -> Ret { (self.fun)(out) } @@ -22,6 +40,14 @@ where /// Used to create a function with destination. /// +/// Creates a wrapper that implements the `To` trait for a closure that +/// operates on a destination type. +/// +/// # Type Parameters +/// * `Dst` - The destination type the closure operates on +/// * `Ret` - The return type of the closure +/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret` +/// /// See the tutorial in [readme.me].. pub fn with_destination(fun: Fun) -> impl To where diff --git a/util/src/fd.rs b/util/src/fd.rs index d9b27bd..05b1abb 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -114,6 +114,10 @@ pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::R } #[cfg(not(target_os = "linux"))] +/// Duplicate a file descriptor, setting the close on exec flag. +/// +/// This is slightly different from [clone_fd_cloexec], as this function supports specifying an +/// explicit destination file descriptor. pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { use rustix::io::{dup2, fcntl_setfd, FdFlags}; dup2(&fd, new)?; @@ -429,4 +433,12 @@ mod tests { assert!(matches!(file.write(&buf), Err(_))); Ok(()) } + + #[test] + fn test_nullfd_read_write() { + let nullfd = open_nullfd().unwrap(); + let mut buf = vec![0u8; 16]; + assert_eq!(rustix::io::read(&nullfd, &mut buf).unwrap(), 0); + assert!(rustix::io::write(&nullfd, b"test").is_err()); + } } diff --git a/util/src/io.rs b/util/src/io.rs index 62c2e63..60747ea 100644 --- a/util/src/io.rs +++ b/util/src/io.rs @@ -292,7 +292,7 @@ impl TryIoErrorKind for T { /// /// See [tutorial in the module](self). pub trait IoResultKindHintExt: Sized { - // Error trait including the ErrorKind hint + /// Error type including the ErrorKind hint type Error; /// Helper for accessing [std::io::Error::kind] in Results /// @@ -318,7 +318,7 @@ impl IoResultKindHintExt for Result { /// /// See [tutorial in the module](self). pub trait TryIoResultKindHintExt: Sized { - // Error trait including the ErrorKind hint + /// Error type including the ErrorKind hint type Error; /// Helper for accessing [std::io::Error::kind] in Results where it may not be present /// diff --git a/util/src/length_prefix_encoding/decoder.rs b/util/src/length_prefix_encoding/decoder.rs index 1769b70..f40d900 100644 --- a/util/src/length_prefix_encoding/decoder.rs +++ b/util/src/length_prefix_encoding/decoder.rs @@ -8,28 +8,37 @@ use crate::{ result::ensure_or, }; +/// Size in bytes of a message header carrying length information pub const HEADER_SIZE: usize = std::mem::size_of::(); #[derive(Error, Debug)] +/// Error enum to represent various boundary sanity check failures during buffer operations pub enum SanityError { #[error("Offset is out of read buffer bounds")] + /// Error indicating that the given offset exceeds the bounds of the read buffer OutOfBufferBounds, #[error("Offset is out of message buffer bounds")] + /// Error indicating that the given offset exceeds the bounds of the message buffer OutOfMessageBounds, } #[derive(Error, Debug)] #[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")] +/// Error indicating that message exceeds available buffer space pub struct MessageTooLargeError { msg_size: usize, buf_size: usize, } impl MessageTooLargeError { + /// Creates a new MessageTooLargeError with the given message and buffer sizes pub fn new(msg_size: usize, buf_size: usize) -> Self { Self { msg_size, buf_size } } + /// Ensures that the message size fits within the buffer size + /// + /// Returns Ok(()) if the message fits, otherwise returns an error with size details pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> { let err = MessageTooLargeError { msg_size, buf_size }; ensure_or(msg_size <= buf_size, err) @@ -37,12 +46,16 @@ impl MessageTooLargeError { } #[derive(Debug)] +/// Return type for ReadFromIo operations that contains the number of bytes read and an optional message slice pub struct ReadFromIoReturn<'a> { + /// Number of bytes read from the input pub bytes_read: usize, + /// Optional slice containing the complete message, if one was read pub message: Option<&'a mut [u8]>, } impl<'a> ReadFromIoReturn<'a> { + /// Creates a new ReadFromIoReturn with the given number of bytes read and optional message slice. pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self { Self { bytes_read, @@ -52,9 +65,12 @@ impl<'a> ReadFromIoReturn<'a> { } #[derive(Debug, Error)] +/// An enum representing errors that can occur during read operations from I/O pub enum ReadFromIoError { + /// Error occurred while reading from the underlying I/O stream #[error("Error reading from the underlying stream")] IoError(#[from] io::Error), + /// Error occurred because message size exceeded buffer capacity #[error("Message size out of buffer bounds")] MessageTooLargeError(#[from] MessageTooLargeError), } @@ -69,6 +85,10 @@ impl TryIoErrorKind for ReadFromIoError { } #[derive(Debug, Default, Clone)] +/// A decoder for length-prefixed messages +/// +/// This struct provides functionality to decode messages that are prefixed with their length. +/// It maintains internal state for header information, the message buffer, and current offset. pub struct LengthPrefixDecoder> { header: [u8; HEADER_SIZE], buf: Buf, @@ -76,25 +96,33 @@ pub struct LengthPrefixDecoder> { } impl> LengthPrefixDecoder { + /// Creates a new LengthPrefixDecoder with the given buffer pub fn new(buf: Buf) -> Self { let header = Default::default(); let off = 0; Self { header, buf, off } } + /// Clears and zeroes all internal state pub fn clear(&mut self) { self.zeroize() } + /// Creates a new LengthPrefixDecoder from its component parts pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self { Self { header, buf, off } } + /// Consumes the decoder and returns its component parts pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) { let Self { header, buf, off } = self; (header, buf, off) } + /// Reads a complete message from the given reader into the decoder. + /// + /// Retries on interrupts and returns the decoded message buffer on success. + /// Returns an error if the read fails or encounters an unexpected EOF. pub fn read_all_from_stdio( &mut self, mut r: R, @@ -125,6 +153,7 @@ impl> LengthPrefixDecoder { } } + /// Reads from the given reader into the decoder's internal buffers pub fn read_from_stdio( &mut self, mut r: R, @@ -150,6 +179,7 @@ impl> LengthPrefixDecoder { }) } + /// Gets the next buffer slice that can be written to pub fn next_slice_to_write_to(&mut self) -> Result, MessageTooLargeError> { fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> { match buf.is_empty() { @@ -172,6 +202,7 @@ impl> LengthPrefixDecoder { Ok(None) } + /// Advances the internal offset by the specified number of bytes pub fn advance(&mut self, count: usize) -> Result<(), SanityError> { let off = self.off + count; let msg_off = off.saturating_sub(HEADER_SIZE); @@ -189,6 +220,7 @@ impl> LengthPrefixDecoder { Ok(()) } + /// Ensures that the internal message buffer is large enough for the message size in the header pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> { let buf_size = self.message_buffer().len(); let msg_size = match self.get_header() { @@ -198,43 +230,53 @@ impl> LengthPrefixDecoder { MessageTooLargeError::ensure(msg_size, buf_size) } + /// Returns a reference to the header buffer pub fn header_buffer(&self) -> &[u8] { &self.header[..] } + /// Returns a mutable reference to the header buffer pub fn header_buffer_mut(&mut self) -> &mut [u8] { &mut self.header[..] } + /// Returns a reference to the message buffer pub fn message_buffer(&self) -> &[u8] { self.buf.borrow() } + /// Returns a mutable reference to the message buffer pub fn message_buffer_mut(&mut self) -> &mut [u8] { self.buf.borrow_mut() } + /// Returns the number of bytes read so far pub fn bytes_read(&self) -> &usize { &self.off } + /// Consumes the decoder and returns just the message buffer pub fn into_message_buffer(self) -> Buf { let Self { buf, .. } = self; buf } + /// Returns the current offset into the header buffer pub fn header_buffer_offset(&self) -> usize { min(self.off, HEADER_SIZE) } + /// Returns the current offset into the message buffer pub fn message_buffer_offset(&self) -> usize { self.off.saturating_sub(HEADER_SIZE) } + /// Returns whether a complete header has been read pub fn has_header(&self) -> bool { self.header_buffer_offset() == HEADER_SIZE } + /// Returns whether a complete message has been read pub fn has_message(&self) -> Result { self.ensure_sufficient_msg_buffer()?; let msg_size = match self.get_header() { @@ -244,46 +286,55 @@ impl> LengthPrefixDecoder { Ok(self.message_buffer_avail().len() == msg_size) } + /// Returns a slice of the available data in the header buffer pub fn header_buffer_avail(&self) -> &[u8] { let off = self.header_buffer_offset(); &self.header_buffer()[..off] } + /// Returns a mutable slice of the available data in the header buffer pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] { let off = self.header_buffer_offset(); &mut self.header_buffer_mut()[..off] } + /// Returns a slice of the remaining space in the header buffer pub fn header_buffer_left(&self) -> &[u8] { let off = self.header_buffer_offset(); &self.header_buffer()[off..] } + /// Returns a mutable slice of the remaining space in the header buffer pub fn header_buffer_left_mut(&mut self) -> &mut [u8] { let off = self.header_buffer_offset(); &mut self.header_buffer_mut()[off..] } + /// Returns a slice of the available data in the message buffer pub fn message_buffer_avail(&self) -> &[u8] { let off = self.message_buffer_offset(); &self.message_buffer()[..off] } + /// Returns a mutable slice of the available data in the message buffer pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] { let off = self.message_buffer_offset(); &mut self.message_buffer_mut()[..off] } + /// Returns a slice of the remaining space in the message buffer pub fn message_buffer_left(&self) -> &[u8] { let off = self.message_buffer_offset(); &self.message_buffer()[off..] } + /// Returns a mutable slice of the remaining space in the message buffer pub fn message_buffer_left_mut(&mut self) -> &mut [u8] { let off = self.message_buffer_offset(); &mut self.message_buffer_mut()[off..] } + /// Returns the message size from the header if available pub fn get_header(&self) -> Option { match self.header_buffer_offset() == HEADER_SIZE { false => None, @@ -291,19 +342,23 @@ impl> LengthPrefixDecoder { } } + /// Returns the size of the message if header is available pub fn message_size(&self) -> Option { self.get_header() } + /// Returns the total size of the encoded message including header pub fn encoded_message_bytes(&self) -> Option { self.message_size().map(|sz| sz + HEADER_SIZE) } + /// Returns a slice of the message fragment if available pub fn message_fragment(&self) -> Result, MessageTooLargeError> { self.ensure_sufficient_msg_buffer()?; Ok(self.message_size().map(|sz| &self.message_buffer()[..sz])) } + /// Returns a mutable slice of the message fragment if available pub fn message_fragment_mut(&mut self) -> Result, MessageTooLargeError> { self.ensure_sufficient_msg_buffer()?; Ok(self @@ -311,12 +366,14 @@ impl> LengthPrefixDecoder { .map(|sz| &mut self.message_buffer_mut()[..sz])) } + /// Returns a slice of the available data in the message fragment pub fn message_fragment_avail(&self) -> Result, MessageTooLargeError> { let off = self.message_buffer_avail().len(); self.message_fragment() .map(|frag| frag.map(|frag| &frag[..off])) } + /// Returns a mutable slice of the available data in the message fragment pub fn message_fragment_avail_mut( &mut self, ) -> Result, MessageTooLargeError> { @@ -325,24 +382,28 @@ impl> LengthPrefixDecoder { .map(|frag| frag.map(|frag| &mut frag[..off])) } + /// Returns a slice of the remaining space in the message fragment pub fn message_fragment_left(&self) -> Result, MessageTooLargeError> { let off = self.message_buffer_avail().len(); self.message_fragment() .map(|frag| frag.map(|frag| &frag[off..])) } + /// Returns a mutable slice of the remaining space in the message fragment pub fn message_fragment_left_mut(&mut self) -> Result, MessageTooLargeError> { let off = self.message_buffer_avail().len(); self.message_fragment_mut() .map(|frag| frag.map(|frag| &mut frag[off..])) } + /// Returns a slice of the complete message if available pub fn message(&self) -> Result, MessageTooLargeError> { let sz = self.message_size(); self.message_fragment_avail() .map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) } + /// Returns a mutable slice of the complete message if available pub fn message_mut(&mut self) -> Result, MessageTooLargeError> { let sz = self.message_size(); self.message_fragment_avail_mut() diff --git a/util/src/length_prefix_encoding/encoder.rs b/util/src/length_prefix_encoding/encoder.rs index a7f86b5..ccc12b1 100644 --- a/util/src/length_prefix_encoding/encoder.rs +++ b/util/src/length_prefix_encoding/encoder.rs @@ -9,46 +9,61 @@ use zeroize::Zeroize; use crate::{io::IoResultKindHintExt, result::ensure_or}; +/// Size of the length prefix header in bytes - equal to the size of a u64 pub const HEADER_SIZE: usize = std::mem::size_of::(); #[derive(Error, Debug, Clone, Copy)] #[error("Write position is out of buffer bounds")] +/// Error type indicating that a write position is beyond the boundaries of the allocated buffer pub struct PositionOutOfBufferBounds; #[derive(Error, Debug, Clone, Copy)] #[error("Write position is out of message bounds")] +/// Error type indicating that a write position is beyond the boundaries of the message pub struct PositionOutOfMessageBounds; #[derive(Error, Debug, Clone, Copy)] #[error("Write position is out of header bounds")] +/// Error type indicating that a write position is beyond the boundaries of the header pub struct PositionOutOfHeaderBounds; #[derive(Error, Debug, Clone, Copy)] #[error("Message length is bigger than buffer length")] +/// Error type indicating that the message length is larger than the available buffer space pub struct MessageTooLarge; #[derive(Error, Debug, Clone, Copy)] +/// Error type for message length sanity checks pub enum MessageLenSanityError { + /// Error indicating position is beyond message boundaries #[error("{0:?}")] PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + /// Error indicating message length exceeds buffer capacity #[error("{0:?}")] MessageTooLarge(#[from] MessageTooLarge), } #[derive(Error, Debug, Clone, Copy)] +/// Error type for position bounds checking pub enum PositionSanityError { + /// Error indicating position is beyond message boundaries #[error("{0:?}")] PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + /// Error indicating position is beyond buffer boundaries #[error("{0:?}")] PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), } #[derive(Error, Debug, Clone, Copy)] +/// Error type combining all sanity check errors pub enum SanityError { + /// Error indicating position is beyond message boundaries #[error("{0:?}")] PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + /// Error indicating position is beyond buffer boundaries #[error("{0:?}")] PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), + /// Error indicating message length exceeds buffer capacity #[error("{0:?}")] MessageTooLarge(#[from] MessageTooLarge), } @@ -86,12 +101,16 @@ impl From for SanityError { } } +/// Result of a write operation on an IO stream pub struct WriteToIoReturn { + /// Number of bytes successfully written in this operation pub bytes_written: usize, + /// Whether the write operation has completed fully pub done: bool, } #[derive(Clone, Copy, Debug)] +/// Length-prefixed encoder that adds a length header to data before writing pub struct LengthPrefixEncoder> { buf: Buf, header: [u8; HEADER_SIZE], @@ -99,6 +118,7 @@ pub struct LengthPrefixEncoder> { } impl> LengthPrefixEncoder { + /// Creates a new encoder from a buffer pub fn from_buffer(buf: Buf) -> Self { let (header, pos) = ([0u8; HEADER_SIZE], 0); let mut r = Self { buf, header, pos }; @@ -106,6 +126,7 @@ impl> LengthPrefixEncoder { r } + /// Creates a new encoder using the full buffer as a message pub fn from_message(msg: Buf) -> Self { let mut r = Self::from_buffer(msg); r.restart_write_with_new_message(r.buffer_bytes().len()) @@ -113,23 +134,27 @@ impl> LengthPrefixEncoder { r } + /// Creates a new encoder using part of the buffer as a message pub fn from_short_message(msg: Buf, len: usize) -> Result { let mut r = Self::from_message(msg); r.set_message_len(len)?; Ok(r) } + /// Creates a new encoder from buffer, message length and write position pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result { let mut r = Self::from_buffer(buf); r.set_msg_len_and_position(len, pos)?; Ok(r) } + /// Consumes the encoder and returns the underlying buffer pub fn into_buffer(self) -> Buf { let Self { buf, .. } = self; buf } + /// Consumes the encoder and returns buffer, message length and write position pub fn into_parts(self) -> (Buf, usize, usize) { let len = self.message_len(); let pos = self.writing_position(); @@ -137,11 +162,13 @@ impl> LengthPrefixEncoder { (buf, len, pos) } + /// Resets the encoder state pub fn clear(&mut self) { self.set_msg_len_and_position(0, 0).unwrap(); self.set_message_offset(0).unwrap(); } + /// Writes the full message to an IO writer, retrying on interrupts pub fn write_all_to_stdio(&mut self, mut w: W) -> io::Result<()> { use io::ErrorKind as K; loop { @@ -158,6 +185,7 @@ impl> LengthPrefixEncoder { } } + /// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status pub fn write_to_stdio(&mut self, mut w: W) -> io::Result { if self.exhausted() { return Ok(WriteToIoReturn { @@ -177,10 +205,12 @@ impl> LengthPrefixEncoder { }) } + /// Resets write position to start for restarting output pub fn restart_write(&mut self) { self.set_writing_position(0).unwrap() } + /// Resets write position to start and updates message length for restarting with new data pub fn restart_write_with_new_message( &mut self, len: usize, @@ -189,6 +219,7 @@ impl> LengthPrefixEncoder { .map_err(|e| e.try_into().unwrap()) } + /// Returns the next unwritten slice of data to write from header or message pub fn next_slice_to_write(&self) -> &[u8] { let s = self.header_left(); if !s.is_empty() { @@ -203,66 +234,82 @@ impl> LengthPrefixEncoder { &[] } + /// Returns true if all data including header and message has been written pub fn exhausted(&self) -> bool { self.next_slice_to_write().is_empty() } + /// Returns slice containing full message data pub fn message(&self) -> &[u8] { &self.buffer_bytes()[..self.message_len()] } + /// Returns slice containing written portion of length header pub fn header_written(&self) -> &[u8] { &self.header()[..self.header_offset()] } + /// Returns slice containing unwritten portion of length header pub fn header_left(&self) -> &[u8] { &self.header()[self.header_offset()..] } + /// Returns slice containing written portion of message data pub fn message_written(&self) -> &[u8] { &self.message()[..self.message_offset()] } + /// Returns slice containing unwritten portion of message data pub fn message_left(&self) -> &[u8] { &self.message()[self.message_offset()..] } + /// Returns reference to underlying buffer pub fn buf(&self) -> &Buf { &self.buf } + /// Returns slice view of underlying buffer bytes pub fn buffer_bytes(&self) -> &[u8] { self.buf().borrow() } + /// Decodes and returns length header value as u64 pub fn decode_header(&self) -> u64 { u64::from_le_bytes(self.header) } + /// Returns slice containing raw length header bytes pub fn header(&self) -> &[u8; HEADER_SIZE] { &self.header } + /// Returns decoded message length from header pub fn message_len(&self) -> usize { self.decode_header() as usize } + /// Returns total encoded size including header and message bytes pub fn encoded_message_bytes(&self) -> usize { self.message_len() + HEADER_SIZE } + /// Returns current write position within header and message pub fn writing_position(&self) -> usize { self.pos } + /// Returns write offset within length header bytes pub fn header_offset(&self) -> usize { min(self.writing_position(), HEADER_SIZE) } + /// Returns write offset within message bytes pub fn message_offset(&self) -> usize { self.writing_position().saturating_sub(HEADER_SIZE) } + /// Sets new length header bytes with bounds checking pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> { self.offset_transaction(|t| { t.header = header; @@ -272,14 +319,17 @@ impl> LengthPrefixEncoder { }) } + /// Encodes and sets length header value with bounds checking pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> { self.set_header(header.to_le_bytes()) } + /// Sets message lengthwith bounds checking pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> { self.encode_and_set_header(len as u64) } + /// Sets write position with message and buffer bounds checking pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> { self.offset_transaction(|t| { t.pos = pos; @@ -289,20 +339,24 @@ impl> LengthPrefixEncoder { }) } + /// Sets write position within header bytes with bounds checking pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> { ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?; self.set_writing_position(off).unwrap(); Ok(()) } + /// Sets write position within message bytes with bounds checking pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> { self.set_writing_position(off + HEADER_SIZE) } + /// Advances write position by specified offset with bounds checking pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> { self.set_writing_position(self.writing_position() + off) } + /// Sets message length and write position with bounds checking pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> { self.pos = 0; self.set_message_len(len)?; @@ -347,24 +401,29 @@ impl> LengthPrefixEncoder { } impl> LengthPrefixEncoder { + /// Gets a mutable reference to the underlying buffer pub fn buf_mut(&mut self) -> &mut Buf { &mut self.buf } + /// Gets the buffer as mutable bytes pub fn buffer_bytes_mut(&mut self) -> &mut [u8] { self.buf.borrow_mut() } + /// Gets a mutable reference to the message slice pub fn message_mut(&mut self) -> &mut [u8] { let off = self.message_len(); &mut self.buffer_bytes_mut()[..off] } + /// Gets a mutable reference to the written portion of the message pub fn message_written_mut(&mut self) -> &mut [u8] { let off = self.message_offset(); &mut self.message_mut()[..off] } + /// Gets a mutable reference to the unwritten portion of the message pub fn message_left_mut(&mut self) -> &mut [u8] { let off = self.message_offset(); &mut self.message_mut()[off..] diff --git a/util/src/length_prefix_encoding/mod.rs b/util/src/length_prefix_encoding/mod.rs index a4d5bf5..5d32cf7 100644 --- a/util/src/length_prefix_encoding/mod.rs +++ b/util/src/length_prefix_encoding/mod.rs @@ -1,2 +1,4 @@ +/// Module that handles decoding functionality pub mod decoder; +/// Module that handles encoding functionality pub mod encoder; diff --git a/util/src/lib.rs b/util/src/lib.rs index adfb11b..ca65b47 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -2,19 +2,37 @@ #![warn(clippy::missing_docs_in_private_items)] #![recursion_limit = "256"] +//! Core utility functions and types used across the codebase. + +/// Base64 encoding and decoding functionality. pub mod b64; +/// Build-time utilities and macros. pub mod build; +/// Control flow abstractions and utilities. pub mod controlflow; +/// File descriptor utilities. pub mod fd; +/// File system operations and handling. pub mod file; +/// Functional programming utilities. pub mod functional; +/// Input/output operations. pub mod io; +/// Length prefix encoding schemes implementation. pub mod length_prefix_encoding; +/// Memory manipulation and allocation utilities. pub mod mem; +/// MIO integration utilities. pub mod mio; +/// Extended Option type functionality. pub mod option; +/// Extended Result type functionality. pub mod result; +/// Time and duration utilities. pub mod time; +/// Type-level numbers and arithmetic. pub mod typenum; +/// Zero-copy serialization utilities. pub mod zerocopy; +/// Memory wiping utilities. pub mod zeroize; diff --git a/util/src/mem.rs b/util/src/mem.rs index e6e22cb..16f290f 100644 --- a/util/src/mem.rs +++ b/util/src/mem.rs @@ -22,6 +22,7 @@ macro_rules! cat { } // TODO: consistent inout ordering +/// Copy all bytes from `src` to `dst`. The lengths must match. pub fn cpy + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) { dst.borrow_mut().copy_from_slice(src.borrow()); } @@ -41,11 +42,13 @@ pub struct Forgetting { } impl Forgetting { + /// Creates a new `Forgetting` instance containing the given value. pub fn new(value: T) -> Self { let value = Some(value); Self { value } } + /// Extracts and returns the contained value, consuming self. pub fn extract(mut self) -> T { let mut value = None; swap(&mut value, &mut self.value); @@ -93,7 +96,9 @@ impl Drop for Forgetting { } } +/// A trait that provides a method to discard a value without explicitly handling its results. pub trait DiscardResultExt { + /// Consumes and discards a value without doing anything with it. fn discard_result(self); } @@ -101,7 +106,9 @@ impl DiscardResultExt for T { fn discard_result(self) {} } +/// Trait that provides a method to explicitly forget values. pub trait ForgetExt { + /// Consumes and forgets a value, preventing its destructor from running. fn forget(self); } @@ -111,8 +118,11 @@ impl ForgetExt for T { } } +/// Extension trait that provides methods for swapping values. pub trait SwapWithExt { + /// Takes ownership of `other` and swaps its value with `self`, returning the original value. fn swap_with(&mut self, other: Self) -> Self; + /// Swaps the values between `self` and `other` in place. fn swap_with_mut(&mut self, other: &mut Self); } @@ -127,7 +137,9 @@ impl SwapWithExt for T { } } +/// Extension trait that provides methods for swapping values with default values. pub trait SwapWithDefaultExt { + /// Takes the current value and replaces it with the default value, returning the original. fn swap_with_default(&mut self) -> Self; } @@ -137,6 +149,7 @@ impl SwapWithDefaultExt for T { } } +/// Extension trait that provides a method to explicitly move values. pub trait MoveExt { /// Deliberately move the value /// diff --git a/util/src/mio/mio.rs b/util/src/mio/mio.rs index 7197126..1df9310 100644 --- a/util/src/mio/mio.rs +++ b/util/src/mio/mio.rs @@ -6,14 +6,23 @@ use crate::{ result::OkExt, }; +/// Module containing I/O interest flags for Unix operations pub mod interest { use mio::Interest; + + /// Interest flag indicating readability pub const R: Interest = Interest::READABLE; + + /// Interest flag indicating writability pub const W: Interest = Interest::WRITABLE; + + /// Interest flag indicating both readability and writability pub const RW: Interest = R.add(W); } +/// Extension trait providing additional functionality for Unix listener pub trait UnixListenerExt: Sized { + /// Creates a new Unix listener by claiming ownership of a raw file descriptor fn claim_fd(fd: RawFd) -> anyhow::Result; } @@ -27,9 +36,15 @@ impl UnixListenerExt for UnixListener { } } +/// Extension trait providing additional functionality for Unix streams pub trait UnixStreamExt: Sized { + /// Creates a new Unix stream from an owned file descriptor fn from_fd(fd: OwnedFd) -> anyhow::Result; + + /// Claims ownership of a raw file descriptor and creates a new Unix stream fn claim_fd(fd: RawFd) -> anyhow::Result; + + /// Claims ownership of a raw file descriptor in place and creates a new Unix stream fn claim_fd_inplace(fd: RawFd) -> anyhow::Result; } diff --git a/util/src/mio/uds_recv_fd.rs b/util/src/mio/uds_recv_fd.rs index a6aef2a..834b10c 100644 --- a/util/src/mio/uds_recv_fd.rs +++ b/util/src/mio/uds_recv_fd.rs @@ -9,6 +9,9 @@ use uds::UnixStreamExt as FdPassingExt; use crate::fd::{claim_fd_inplace, IntoStdioErr}; +/// A wrapper around a socket that combines reading from the socket with tracking +/// received file descriptors. Limits the maximum number of file descriptors that +/// can be received in a single read operation via the `MAX_FDS` parameter. pub struct ReadWithFileDescriptors where Sock: FdPassingExt, @@ -27,6 +30,8 @@ where BorrowSock: Borrow, BorrowFds: BorrowMut>, { + /// Creates a new `ReadWithFileDescriptors` by wrapping a socket and a file + /// descriptor queue. pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self { let _sock_dummy = PhantomData; Self { @@ -36,19 +41,24 @@ where } } + /// Consumes the wrapper and returns the underlying socket and file + /// descriptor queue. pub fn into_parts(self) -> (BorrowSock, BorrowFds) { let Self { socket, fds, .. } = self; (socket, fds) } + /// Returns a reference to the underlying socket. pub fn socket(&self) -> &Sock { self.socket.borrow() } + /// Returns a reference to the file descriptor queue. pub fn fds(&self) -> &VecDeque { self.fds.borrow() } + /// Returns a mutable reference to the file descriptor queue. pub fn fds_mut(&mut self) -> &mut VecDeque { self.fds.borrow_mut() } @@ -61,6 +71,7 @@ where BorrowSock: BorrowMut, BorrowFds: BorrowMut>, { + /// Returns a mutable reference to the underlying socket. pub fn socket_mut(&mut self) -> &mut Sock { self.socket.borrow_mut() } diff --git a/util/src/mio/uds_send_fd.rs b/util/src/mio/uds_send_fd.rs index 308adee..cd5db4f 100644 --- a/util/src/mio/uds_send_fd.rs +++ b/util/src/mio/uds_send_fd.rs @@ -10,6 +10,7 @@ use uds::UnixStreamExt as FdPassingExt; use crate::{repeat, return_if}; +/// A structure that facilitates writing data and file descriptors to a Unix domain socket pub struct WriteWithFileDescriptors where Sock: FdPassingExt, @@ -30,6 +31,7 @@ where BorrowSock: Borrow, BorrowFds: BorrowMut>, { + /// Creates a new `WriteWithFileDescriptors` instance with the given socket and file descriptor queue pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self { let _sock_dummy = PhantomData; let _fd_dummy = PhantomData; @@ -41,19 +43,23 @@ where } } + /// Consumes this instance and returns the underlying socket and file descriptor queue pub fn into_parts(self) -> (BorrowSock, BorrowFds) { let Self { socket, fds, .. } = self; (socket, fds) } + /// Returns a reference to the underlying socket pub fn socket(&self) -> &Sock { self.socket.borrow() } + /// Returns a reference to the file descriptor queue pub fn fds(&self) -> &VecDeque { self.fds.borrow() } + /// Returns a mutable reference to the file descriptor queue pub fn fds_mut(&mut self) -> &mut VecDeque { self.fds.borrow_mut() } @@ -66,6 +72,7 @@ where BorrowSock: BorrowMut, BorrowFds: BorrowMut>, { + /// Returns a mutable reference to the underlying socket pub fn socket_mut(&mut self) -> &mut Sock { self.socket.borrow_mut() } diff --git a/util/src/option.rs b/util/src/option.rs index 6b743df..a6d7050 100644 --- a/util/src/option.rs +++ b/util/src/option.rs @@ -1,4 +1,17 @@ +/// A helper trait for turning any type value into `Some(value)`. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::option::SomeExt; +/// +/// let x = 42; +/// let y = x.some(); +/// +/// assert_eq!(y, Some(42)); +/// ``` pub trait SomeExt: Sized { + /// Wraps the calling value in `Some()`. fn some(self) -> Option { Some(self) } diff --git a/util/src/result.rs b/util/src/result.rs index 041adbd..3c72ed9 100644 --- a/util/src/result.rs +++ b/util/src/result.rs @@ -8,7 +8,9 @@ macro_rules! attempt { }; } +/// Trait for the ok operation, which provides a way to convert a value into a Result pub trait OkExt: Sized { + /// Wraps a value in a Result::Ok variant fn ok(self) -> Result; } @@ -25,6 +27,7 @@ impl OkExt for T { /// /// Implementations must not panic. pub trait GuaranteedValue { + /// The value type that will be returned by guaranteed() type Value; /// Extract the contained value while being panic-safe, like .unwrap() @@ -35,7 +38,11 @@ pub trait GuaranteedValue { fn guaranteed(self) -> Self::Value; } +/// Extension trait for adding finally operation to types pub trait FinallyExt { + /// Executes a closure with mutable access to self and returns self + /// + /// The closure is guaranteed to be executed before returning. fn finally(self, f: F) -> Self; } @@ -125,6 +132,18 @@ impl GuaranteedValue for Guaranteed { } } +/// Checks a condition is true and returns an error if not. +/// +/// # Examples +/// +/// ```rust +/// # use rosenpass_util::result::ensure_or; +/// let result = ensure_or(5 > 3, "not greater"); +/// assert!(result.is_ok()); +/// +/// let result = ensure_or(5 < 3, "not less"); +/// assert!(result.is_err()); +/// ``` pub fn ensure_or(b: bool, err: E) -> Result<(), E> { match b { true => Ok(()), @@ -132,6 +151,18 @@ pub fn ensure_or(b: bool, err: E) -> Result<(), E> { } } +/// Evaluates to an error if the condition is true. +/// +/// # Examples +/// +/// ```rust +/// # use rosenpass_util::result::bail_if; +/// let result = bail_if(false, "not bailed"); +/// assert!(result.is_ok()); +/// +/// let result = bail_if(true, "bailed"); +/// assert!(result.is_err()); +/// ``` pub fn bail_if(b: bool, err: E) -> Result<(), E> { ensure_or(!b, err) } diff --git a/util/src/time.rs b/util/src/time.rs index 222ff42..ba28005 100644 --- a/util/src/time.rs +++ b/util/src/time.rs @@ -5,6 +5,16 @@ use std::time::Instant; /// This is a simple wrapper around `std::time::Instant` that provides a /// convenient way to get the seconds elapsed since the creation of the /// `Timebase` instance. +/// +/// # Examples +/// +/// ``` +/// use rosenpass_util::time::Timebase; +/// +/// let timebase = Timebase::default(); +/// let now = timebase.now(); +/// assert!(now > 0.0); +/// ``` #[derive(Clone, Debug)] pub struct Timebase(Instant); diff --git a/util/src/typenum.rs b/util/src/typenum.rs index 2f1d32f..73ccfff 100644 --- a/util/src/typenum.rs +++ b/util/src/typenum.rs @@ -16,6 +16,7 @@ macro_rules! typenum2const { /// Trait implemented by constant integers to facilitate conversion to constant integers pub trait IntoConst { + /// The constant value after conversion const VALUE: T; } diff --git a/util/src/zerocopy/ref_maker.rs b/util/src/zerocopy/ref_maker.rs index 0b18702..1ba24e8 100644 --- a/util/src/zerocopy/ref_maker.rs +++ b/util/src/zerocopy/ref_maker.rs @@ -7,56 +7,68 @@ use zeroize::Zeroize; use crate::zeroize::ZeroizedExt; #[derive(Clone, Copy, Debug)] +/// A convenience type for working with mutable references to a buffer and an +/// expected target type. pub struct RefMaker { buf: B, _phantom_t: PhantomData, } impl RefMaker { + /// Creates a new RefMaker with the given buffer pub fn new(buf: B) -> Self { let _phantom_t = PhantomData; Self { buf, _phantom_t } } + /// Returns the size in bytes needed for target type T pub const fn target_size() -> usize { std::mem::size_of::() } + /// Consumes this RefMaker and returns the inner buffer pub fn into_buf(self) -> B { self.buf } + /// Returns a reference to the inner buffer pub fn buf(&self) -> &B { &self.buf } + /// Returns a mutable reference to the inner buffer pub fn buf_mut(&mut self) -> &mut B { &mut self.buf } } impl RefMaker { + /// Parses the buffer into a reference of type T pub fn parse(self) -> anyhow::Result> { self.ensure_fit()?; Ref::::new(self.buf).context("Parser error!") } + /// Splits the buffer into a RefMaker containing the first `target_size` bytes and the remaining tail pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> { self.ensure_fit()?; let (head, tail) = self.buf.split_at(Self::target_size()); Ok((Self::new(head), tail)) } + /// Splits the buffer into two RefMakers, with the first containing the first `target_size` bytes pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> { self.ensure_fit()?; let (head, tail) = self.buf.split_at(Self::target_size()); Ok((Self::new(head), Self::new(tail))) } + /// Returns a RefMaker containing only the first `target_size` bytes pub fn from_prefix(self) -> anyhow::Result { Ok(Self::from_prefix_with_tail(self)?.0) } + /// Splits the buffer into a RefMaker containing the last `target_size` bytes and the preceding head pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> { self.ensure_fit()?; let point = self.bytes().len() - Self::target_size(); @@ -64,6 +76,7 @@ impl RefMaker { Ok((Self::new(tail), head)) } + /// Splits the buffer into two RefMakers, with the second containing the last `target_size` bytes pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> { self.ensure_fit()?; let point = self.bytes().len() - Self::target_size(); @@ -71,14 +84,17 @@ impl RefMaker { Ok((Self::new(head), Self::new(tail))) } + /// Returns a RefMaker containing only the last `target_size` bytes pub fn from_suffix(self) -> anyhow::Result { Ok(Self::from_suffix_with_head(self)?.0) } + /// Returns a reference to the underlying bytes pub fn bytes(&self) -> &[u8] { self.buf().deref() } + /// Ensures the buffer is large enough to hold type T pub fn ensure_fit(&self) -> anyhow::Result<()> { let have = self.bytes().len(); let need = Self::target_size(); @@ -91,10 +107,12 @@ impl RefMaker { } impl RefMaker { + /// Creates a zeroed reference of type T from the buffer pub fn make_zeroized(self) -> anyhow::Result> { self.zeroized().parse() } + /// Returns a mutable reference to the underlying bytes pub fn bytes_mut(&mut self) -> &mut [u8] { self.buf_mut().deref_mut() } diff --git a/util/src/zerocopy/zerocopy_ref_ext.rs b/util/src/zerocopy/zerocopy_ref_ext.rs index 1acc52a..f8eecc7 100644 --- a/util/src/zerocopy/zerocopy_ref_ext.rs +++ b/util/src/zerocopy/zerocopy_ref_ext.rs @@ -1,10 +1,14 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref}; +/// A trait for converting a `Ref` into a `Ref<&[u8], T>`. pub trait ZerocopyEmancipateExt { + /// Converts this reference into a reference backed by a byte slice. fn emancipate(&self) -> Ref<&[u8], T>; } +/// A trait for converting a `Ref` into a mutable `Ref<&mut [u8], T>`. pub trait ZerocopyEmancipateMutExt { + /// Converts this reference into a mutable reference backed by a byte slice. fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>; } diff --git a/util/src/zerocopy/zerocopy_slice_ext.rs b/util/src/zerocopy/zerocopy_slice_ext.rs index 1399253..ef068d5 100644 --- a/util/src/zerocopy/zerocopy_slice_ext.rs +++ b/util/src/zerocopy/zerocopy_slice_ext.rs @@ -2,19 +2,24 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref}; use super::RefMaker; +/// Extension trait for zero-copy slice operations. pub trait ZerocopySliceExt: Sized + ByteSlice { + /// Creates a new `RefMaker` for the given slice. fn zk_ref_maker(self) -> RefMaker { RefMaker::::new(self) } + /// Parses the slice into a zero-copy reference. fn zk_parse(self) -> anyhow::Result> { self.zk_ref_maker().parse() } + /// Parses a prefix of the slice into a zero-copy reference. fn zk_parse_prefix(self) -> anyhow::Result> { self.zk_ref_maker().from_prefix()?.parse() } + /// Parses a suffix of the slice into a zero-copy reference. fn zk_parse_suffix(self) -> anyhow::Result> { self.zk_ref_maker().from_suffix()?.parse() } @@ -22,15 +27,19 @@ pub trait ZerocopySliceExt: Sized + ByteSlice { impl ZerocopySliceExt for B {} +/// Extension trait for zero-copy slice operations with mutable slices. pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut { + /// Creates a new zeroed reference from the entire slice. fn zk_zeroized(self) -> anyhow::Result> { self.zk_ref_maker().make_zeroized() } + /// Creates a new zeroed reference from a prefix of the slice. fn zk_zeroized_from_prefix(self) -> anyhow::Result> { self.zk_ref_maker().from_prefix()?.make_zeroized() } + /// Creates a new zeroed reference from a suffix of the slice. fn zk_zeroized_from_suffix(self) -> anyhow::Result> { self.zk_ref_maker().from_suffix()?.make_zeroized() } diff --git a/util/src/zeroize/zeroized_ext.rs b/util/src/zeroize/zeroized_ext.rs index c4f87d5..68fda45 100644 --- a/util/src/zeroize/zeroized_ext.rs +++ b/util/src/zeroize/zeroized_ext.rs @@ -1,6 +1,22 @@ use zeroize::Zeroize; +/// Extension trait providing a method for zeroizing a value and returning it +/// +/// # Examples +/// +/// ```rust +/// use zeroize::Zeroize; +/// use rosenpass_util::zeroize::ZeroizedExt; +/// +/// let mut value = String::from("hello"); +/// value.zeroize(); +/// assert_eq!(value, ""); +/// +/// let value = String::from("hello").zeroized(); +/// assert_eq!(value, ""); +/// ``` pub trait ZeroizedExt: Zeroize + Sized { + /// Zeroizes the value in place and returns self fn zeroized(mut self) -> Self { self.zeroize(); self diff --git a/wireguard-broker/Cargo.toml b/wireguard-broker/Cargo.toml index 11e0b4b..203afe7 100644 --- a/wireguard-broker/Cargo.toml +++ b/wireguard-broker/Cargo.toml @@ -28,7 +28,7 @@ derive_builder = { workspace = true } postcard = { workspace = true } # Problem in CI, unknown reasons: dependency (libc) specified without providing a local path, Git repository, version, or workspace dependency to use # Maybe something about the combination of features and optional crates? -rustix = { version = "0.38.39", optional = true } +rustix = { version = "0.38.40", optional = true } libc = { version = "0.2", optional = true } # Mio broker client