Merge branch 'main' into rustix

This commit is contained in:
Karolin Varner
2024-11-28 21:01:07 +01:00
committed by GitHub
30 changed files with 462 additions and 66 deletions

View File

@@ -8,7 +8,7 @@ If any other issue occurs
1. Make sure you locally checked out the head of the main branch 1. Make sure you locally checked out the head of the main branch
- `git stash --include-untracked && git checkout main && git pull` - `git stash --include-untracked && git checkout main && git pull`
2. Make sure all tests pass 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) 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 - 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`, ...) 4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)

32
Cargo.lock generated
View File

@@ -1210,9 +1210,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.162" version = "0.2.166"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
[[package]] [[package]]
name = "libcrux" name = "libcrux"
@@ -2058,9 +2058,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.39" version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@@ -2113,18 +2113,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.214" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.214" version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2154,9 +2154,9 @@ dependencies = [
[[package]] [[package]]
name = "serial_test" name = "serial_test"
version = "3.1.1" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
dependencies = [ dependencies = [
"futures", "futures",
"log", "log",
@@ -2168,9 +2168,9 @@ dependencies = [
[[package]] [[package]]
name = "serial_test_derive" name = "serial_test_derive"
version = "3.1.1" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2339,18 +2339,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.68" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.68" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -35,7 +35,7 @@ doc-comment = "0.3.3"
base64ct = { version = "1.6.0", default-features = false } base64ct = { version = "1.6.0", default-features = false }
zeroize = "1.8.1" zeroize = "1.8.1"
memoffset = "0.9.1" memoffset = "0.9.1"
thiserror = "1.0.68" thiserror = "1.0.69"
paste = "1.0.15" paste = "1.0.15"
env_logger = "0.10.2" env_logger = "0.10.2"
toml = "0.7.8" toml = "0.7.8"
@@ -50,7 +50,7 @@ log = { version = "0.4.22" }
clap = { version = "4.5.20", features = ["derive"] } clap = { version = "4.5.20", features = ["derive"] }
clap_mangen = "0.2.24" clap_mangen = "0.2.24"
clap_complete = "4.5.37" 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"] } arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.93", features = ["backtrace", "std"] } anyhow = { version = "1.0.93", features = ["backtrace", "std"] }
mio = { version = "1.0.2", features = ["net", "os-poll"] } mio = { version = "1.0.2", features = ["net", "os-poll"] }
@@ -76,7 +76,7 @@ libc = { version = "0.2" }
uds = { git = "https://github.com/rosenpass/uds" } uds = { git = "https://github.com/rosenpass/uds" }
#Dev dependencies #Dev dependencies
serial_test = "3.1.1" serial_test = "3.2.0"
tempfile = "3" tempfile = "3"
stacker = "0.1.17" stacker = "0.1.17"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
@@ -89,4 +89,4 @@ procspawn = { version = "1.0.1", features = ["test-support"] }
#Broker dependencies (might need cleanup or changes) #Broker dependencies (might need cleanup or changes)
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] } wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
command-fds = "0.2.3" command-fds = "0.2.3"
rustix = { version = "0.38.39", features = ["net", "fs"] } rustix = { version = "0.38.40", features = ["net", "fs"] }

View File

@@ -88,7 +88,7 @@ impl MioConnection {
}) })
} }
pub fn shoud_close(&self) -> bool { pub fn should_close(&self) -> bool {
let exhausted = self let exhausted = self
.buffers .buffers
.as_ref() .as_ref()
@@ -262,7 +262,7 @@ pub trait MioConnectionContext {
} }
fn should_close(&self) -> bool { fn should_close(&self) -> bool {
self.mio_connection().shoud_close() self.mio_connection().should_close()
} }
} }

View File

@@ -43,15 +43,15 @@ pub enum BrokerInterface {
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)] #[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs { 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")] #[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
log_level: Option<log::LevelFilter>, log_level: Option<log::LevelFilter>,
/// show verbose log output sets log level to "debug" /// Show verbose log output sets log level to "debug"
#[arg(short, long, group = "log-level")] #[arg(short, long, group = "log-level")]
verbose: bool, 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")] #[arg(short, long, group = "log-level")]
quiet: bool, quiet: bool,
@@ -59,22 +59,23 @@ pub struct CliArgs {
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
api: crate::api::cli::ApiCli, 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")] #[cfg(feature = "experiment_api")]
#[arg(long, group = "psk-broker-specs")] #[arg(long, group = "psk-broker-specs")]
psk_broker_path: Option<PathBuf>, psk_broker_path: Option<PathBuf>,
/// 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 /// When this command is called from another process, the other process can
/// Unix socket for the psk broker connection to use themselves, passing it to this process -- /// open and bind the Unix socket for the PSK broker connection to use
/// in Rust this can be achieved using the /// themselves, passing it to this process - in Rust this can be achieved
/// [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/)
/// crate
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
#[arg(long, group = "psk-broker-specs")] #[arg(long, group = "psk-broker-specs")]
psk_broker_fd: Option<i32>, psk_broker_fd: Option<i32>,
/// spawn a psk broker locally using a socket pair /// Spawn a PSK broker locally using a socket pair
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
#[arg(short, long, group = "psk-broker-specs")] #[arg(short, long, group = "psk-broker-specs")]
psk_broker_spawn: bool, psk_broker_spawn: bool,
@@ -82,11 +83,16 @@ pub struct CliArgs {
#[command(subcommand)] #[command(subcommand)]
pub command: Option<CliCommand>, pub command: Option<CliCommand>,
/// 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")] #[clap(long, value_name = "out_dir")]
pub generate_manpage: Option<PathBuf>, pub generate_manpage: Option<PathBuf>,
/// Generate completion file for a shell /// Generate completion file for a shell
///
/// This option is used to generate completion files for the specified shell
#[clap(long, value_name = "shell")] #[clap(long, value_name = "shell")]
pub print_completions: Option<clap_complete::Shell>, pub print_completions: Option<clap_complete::Shell>,
} }
@@ -143,20 +149,20 @@ impl CliArgs {
/// represents a command specified via CLI /// represents a command specified via CLI
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
pub enum CliCommand { 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 /// This will parse the configuration file and perform key exchanges with
/// with the specified peers. If a peer's endpoint is specified, this /// the specified peers. If a peer's endpoint is specified, this Rosenpass
/// Rosenpass instance will try to initiate a key exchange with the peer, /// instance will try to initiate a key exchange with the peer; otherwise,
/// otherwise only initiation attempts from the peer will be responded to. /// only initiation attempts from other peers will be responded to.
ExchangeConfig { config_file: PathBuf }, 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 /// The configuration is read from the command line. The `peer` token always
/// always separates multiple peers, e. g. if the token `peer` appears /// separates multiple peers, e.g., if the token `peer` appears in the
/// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments /// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but
/// but instead a new peer is created. /// instead a new peer is created.
/* Explanation: `first_arg` and `rest_of_args` are combined into one /* Explanation: `first_arg` and `rest_of_args` are combined into one
* `Vec<String>`. They are only used to trick clap into displaying some * `Vec<String>`. They are only used to trick clap into displaying some
* guidance on the CLI usage. * guidance on the CLI usage.
@@ -185,7 +191,10 @@ pub enum CliCommand {
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
}, },
/// 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 { GenConfig {
config_file: PathBuf, config_file: PathBuf,
@@ -194,19 +203,19 @@ pub enum CliCommand {
force: bool, 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 /// Generates secret & public key to their destination. If a config file is
/// is provided then the key file destination is taken from there. /// provided then the key file destination is taken from there, otherwise
/// Otherwise the /// the destination is taken from the CLI arguments.
GenKeys { GenKeys {
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
/// where to write public-key to /// Where to write public key to
#[clap(short, long)] #[clap(short, long)]
public_key: Option<PathBuf>, public_key: Option<PathBuf>,
/// where to write secret-key to /// Where to write secret key to
#[clap(short, long)] #[clap(short, long)]
secret_key: Option<PathBuf>, secret_key: Option<PathBuf>,
@@ -215,21 +224,27 @@ pub enum CliCommand {
force: bool, 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<PathBuf> },
/// DEPRECATED - use the gen-keys command instead
#[allow(rustdoc::broken_intra_doc_links)] #[allow(rustdoc::broken_intra_doc_links)]
#[allow(rustdoc::invalid_html_tags)] #[allow(rustdoc::invalid_html_tags)]
#[command(hide = true)]
Keygen { 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 <PATH> private-key <PATH> /// public-key <PATH> private-key <PATH>
args: Vec<String>, args: Vec<String>,
}, },
/// Validate a configuration
Validate { config_files: Vec<PathBuf> },
} }
impl CliArgs { impl CliArgs {
/// runs the command specified via CLI /// Runs the command specified via CLI
/// ///
/// ## TODO /// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...

View File

@@ -1,3 +1,5 @@
#![warn(missing_docs)]
#![recursion_limit = "256"]
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#[cfg(doctest)] #[cfg(doctest)]

View File

@@ -5,23 +5,70 @@ use crate::CondenseBeside;
pub struct Beside<Val, Ret>(pub Val, pub Ret); pub struct Beside<Val, Ret>(pub Val, pub Ret);
impl<Val, Ret> Beside<Val, Ret> { impl<Val, Ret> Beside<Val, Ret> {
/// 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 { pub fn dest(&self) -> &Val {
&self.0 &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 { pub fn ret(&self) -> &Ret {
&self.1 &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 { pub fn dest_mut(&mut self) -> &mut Val {
&mut self.0 &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 { pub fn ret_mut(&mut self) -> &mut Ret {
&mut self.1 &mut self.1
} }
/// Perform beside condensation. See [CondenseBeside] /// 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) -> <Ret as CondenseBeside<Val>>::Condensed pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
where where
Ret: CondenseBeside<Val>, Ret: CondenseBeside<Val>,

View File

@@ -7,8 +7,10 @@
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the /// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
/// condense trait. /// condense trait.
pub trait CondenseBeside<Val> { pub trait CondenseBeside<Val> {
/// The type that results from condensation.
type Condensed; type Condensed;
/// Takes ownership of `self` and condenses it with the given value.
fn condense(self, ret: Val) -> Self::Condensed; fn condense(self, ret: Val) -> Self::Condensed;
} }

View File

@@ -1,6 +1,7 @@
/// Helper performing explicit unsized coercion. /// Helper performing explicit unsized coercion.
/// Used by the [to](crate::to()) function. /// Used by the [to](crate::to()) function.
pub trait DstCoercion<Dst: ?Sized> { pub trait DstCoercion<Dst: ?Sized> {
/// Performs an explicit coercion to the destination type.
fn coerce_dest(&mut self) -> &mut Dst; fn coerce_dest(&mut self) -> &mut Dst;
} }

View File

@@ -1,13 +1,16 @@
use crate::{Beside, CondenseBeside}; use crate::{Beside, CondenseBeside};
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
// The To trait is the core of the to crate; most functions with destinations will either return /// 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<Destination, /// an object that is an instance of this trait or they will return `-> impl To<Destination,
// Return_value`. /// Return_value`.
// ///
// A quick way to implement a function with destination is to use the /// A quick way to implement a function with destination is to use the
// [with_destination(|param: &mut Type| ...)] higher order function. /// [with_destination(|param: &mut Type| ...)] higher order function.
pub trait To<Dst: ?Sized, Ret>: Sized { pub trait To<Dst: ?Sized, Ret>: 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; fn to(self, out: &mut Dst) -> Ret;
/// Generate a destination on the fly with a lambda. /// Generate a destination on the fly with a lambda.

View File

@@ -1,20 +1,38 @@
use crate::To; use crate::To;
use std::marker::PhantomData; 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<Dst, Ret, Fun> struct ToClosure<Dst, Ret, Fun>
where where
Dst: ?Sized, Dst: ?Sized,
Fun: FnOnce(&mut Dst) -> Ret, Fun: FnOnce(&mut Dst) -> Ret,
{ {
/// The function to call.
fun: Fun, fun: Fun,
/// Phantom data to hold the destination type
_val: PhantomData<Box<Dst>>, _val: PhantomData<Box<Dst>>,
} }
/// Implementation of the `To` trait for ToClosure
///
/// This enables calling the wrapped closure with a destination reference.
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun> impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
where where
Dst: ?Sized, Dst: ?Sized,
Fun: FnOnce(&mut Dst) -> Ret, 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 { fn to(self, out: &mut Dst) -> Ret {
(self.fun)(out) (self.fun)(out)
} }
@@ -22,6 +40,14 @@ where
/// Used to create a function with destination. /// 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].. /// See the tutorial in [readme.me]..
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret> pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where where

View File

@@ -114,6 +114,10 @@ pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::R
} }
#[cfg(not(target_os = "linux"))] #[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: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
use rustix::io::{dup2, fcntl_setfd, FdFlags}; use rustix::io::{dup2, fcntl_setfd, FdFlags};
dup2(&fd, new)?; dup2(&fd, new)?;
@@ -429,4 +433,12 @@ mod tests {
assert!(matches!(file.write(&buf), Err(_))); assert!(matches!(file.write(&buf), Err(_)));
Ok(()) 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());
}
} }

View File

@@ -292,7 +292,7 @@ impl<T: IoErrorKind> TryIoErrorKind for T {
/// ///
/// See [tutorial in the module](self). /// See [tutorial in the module](self).
pub trait IoResultKindHintExt<T>: Sized { pub trait IoResultKindHintExt<T>: Sized {
// Error trait including the ErrorKind hint /// Error type including the ErrorKind hint
type Error; type Error;
/// Helper for accessing [std::io::Error::kind] in Results /// Helper for accessing [std::io::Error::kind] in Results
/// ///
@@ -318,7 +318,7 @@ impl<T, E: IoErrorKind> IoResultKindHintExt<T> for Result<T, E> {
/// ///
/// See [tutorial in the module](self). /// See [tutorial in the module](self).
pub trait TryIoResultKindHintExt<T>: Sized { pub trait TryIoResultKindHintExt<T>: Sized {
// Error trait including the ErrorKind hint /// Error type including the ErrorKind hint
type Error; type Error;
/// Helper for accessing [std::io::Error::kind] in Results where it may not be present /// Helper for accessing [std::io::Error::kind] in Results where it may not be present
/// ///

View File

@@ -8,28 +8,37 @@ use crate::{
result::ensure_or, result::ensure_or,
}; };
/// Size in bytes of a message header carrying length information
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>(); pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug)] #[derive(Error, Debug)]
/// Error enum to represent various boundary sanity check failures during buffer operations
pub enum SanityError { pub enum SanityError {
#[error("Offset is out of read buffer bounds")] #[error("Offset is out of read buffer bounds")]
/// Error indicating that the given offset exceeds the bounds of the read buffer
OutOfBufferBounds, OutOfBufferBounds,
#[error("Offset is out of message buffer bounds")] #[error("Offset is out of message buffer bounds")]
/// Error indicating that the given offset exceeds the bounds of the message buffer
OutOfMessageBounds, OutOfMessageBounds,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")] #[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")]
/// Error indicating that message exceeds available buffer space
pub struct MessageTooLargeError { pub struct MessageTooLargeError {
msg_size: usize, msg_size: usize,
buf_size: usize, buf_size: usize,
} }
impl MessageTooLargeError { impl MessageTooLargeError {
/// Creates a new MessageTooLargeError with the given message and buffer sizes
pub fn new(msg_size: usize, buf_size: usize) -> Self { pub fn new(msg_size: usize, buf_size: usize) -> Self {
Self { msg_size, buf_size } 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> { pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> {
let err = MessageTooLargeError { msg_size, buf_size }; let err = MessageTooLargeError { msg_size, buf_size };
ensure_or(msg_size <= buf_size, err) ensure_or(msg_size <= buf_size, err)
@@ -37,12 +46,16 @@ impl MessageTooLargeError {
} }
#[derive(Debug)] #[derive(Debug)]
/// Return type for ReadFromIo operations that contains the number of bytes read and an optional message slice
pub struct ReadFromIoReturn<'a> { pub struct ReadFromIoReturn<'a> {
/// Number of bytes read from the input
pub bytes_read: usize, pub bytes_read: usize,
/// Optional slice containing the complete message, if one was read
pub message: Option<&'a mut [u8]>, pub message: Option<&'a mut [u8]>,
} }
impl<'a> ReadFromIoReturn<'a> { 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 { pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self {
Self { Self {
bytes_read, bytes_read,
@@ -52,9 +65,12 @@ impl<'a> ReadFromIoReturn<'a> {
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
/// An enum representing errors that can occur during read operations from I/O
pub enum ReadFromIoError { pub enum ReadFromIoError {
/// Error occurred while reading from the underlying I/O stream
#[error("Error reading from the underlying stream")] #[error("Error reading from the underlying stream")]
IoError(#[from] io::Error), IoError(#[from] io::Error),
/// Error occurred because message size exceeded buffer capacity
#[error("Message size out of buffer bounds")] #[error("Message size out of buffer bounds")]
MessageTooLargeError(#[from] MessageTooLargeError), MessageTooLargeError(#[from] MessageTooLargeError),
} }
@@ -69,6 +85,10 @@ impl TryIoErrorKind for ReadFromIoError {
} }
#[derive(Debug, Default, Clone)] #[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<Buf: BorrowMut<[u8]>> { pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
header: [u8; HEADER_SIZE], header: [u8; HEADER_SIZE],
buf: Buf, buf: Buf,
@@ -76,25 +96,33 @@ pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
} }
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> { impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
/// Creates a new LengthPrefixDecoder with the given buffer
pub fn new(buf: Buf) -> Self { pub fn new(buf: Buf) -> Self {
let header = Default::default(); let header = Default::default();
let off = 0; let off = 0;
Self { header, buf, off } Self { header, buf, off }
} }
/// Clears and zeroes all internal state
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.zeroize() self.zeroize()
} }
/// Creates a new LengthPrefixDecoder from its component parts
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self { pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
Self { header, buf, off } Self { header, buf, off }
} }
/// Consumes the decoder and returns its component parts
pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) { pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) {
let Self { header, buf, off } = self; let Self { header, buf, off } = self;
(header, buf, off) (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<R: io::Read>( pub fn read_all_from_stdio<R: io::Read>(
&mut self, &mut self,
mut r: R, mut r: R,
@@ -125,6 +153,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
} }
} }
/// Reads from the given reader into the decoder's internal buffers
pub fn read_from_stdio<R: io::Read>( pub fn read_from_stdio<R: io::Read>(
&mut self, &mut self,
mut r: R, mut r: R,
@@ -150,6 +179,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
}) })
} }
/// Gets the next buffer slice that can be written to
pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> { pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> { fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> {
match buf.is_empty() { match buf.is_empty() {
@@ -172,6 +202,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(None) Ok(None)
} }
/// Advances the internal offset by the specified number of bytes
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> { pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
let off = self.off + count; let off = self.off + count;
let msg_off = off.saturating_sub(HEADER_SIZE); let msg_off = off.saturating_sub(HEADER_SIZE);
@@ -189,6 +220,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(()) 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> { pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> {
let buf_size = self.message_buffer().len(); let buf_size = self.message_buffer().len();
let msg_size = match self.get_header() { let msg_size = match self.get_header() {
@@ -198,43 +230,53 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
MessageTooLargeError::ensure(msg_size, buf_size) MessageTooLargeError::ensure(msg_size, buf_size)
} }
/// Returns a reference to the header buffer
pub fn header_buffer(&self) -> &[u8] { pub fn header_buffer(&self) -> &[u8] {
&self.header[..] &self.header[..]
} }
/// Returns a mutable reference to the header buffer
pub fn header_buffer_mut(&mut self) -> &mut [u8] { pub fn header_buffer_mut(&mut self) -> &mut [u8] {
&mut self.header[..] &mut self.header[..]
} }
/// Returns a reference to the message buffer
pub fn message_buffer(&self) -> &[u8] { pub fn message_buffer(&self) -> &[u8] {
self.buf.borrow() self.buf.borrow()
} }
/// Returns a mutable reference to the message buffer
pub fn message_buffer_mut(&mut self) -> &mut [u8] { pub fn message_buffer_mut(&mut self) -> &mut [u8] {
self.buf.borrow_mut() self.buf.borrow_mut()
} }
/// Returns the number of bytes read so far
pub fn bytes_read(&self) -> &usize { pub fn bytes_read(&self) -> &usize {
&self.off &self.off
} }
/// Consumes the decoder and returns just the message buffer
pub fn into_message_buffer(self) -> Buf { pub fn into_message_buffer(self) -> Buf {
let Self { buf, .. } = self; let Self { buf, .. } = self;
buf buf
} }
/// Returns the current offset into the header buffer
pub fn header_buffer_offset(&self) -> usize { pub fn header_buffer_offset(&self) -> usize {
min(self.off, HEADER_SIZE) min(self.off, HEADER_SIZE)
} }
/// Returns the current offset into the message buffer
pub fn message_buffer_offset(&self) -> usize { pub fn message_buffer_offset(&self) -> usize {
self.off.saturating_sub(HEADER_SIZE) self.off.saturating_sub(HEADER_SIZE)
} }
/// Returns whether a complete header has been read
pub fn has_header(&self) -> bool { pub fn has_header(&self) -> bool {
self.header_buffer_offset() == HEADER_SIZE self.header_buffer_offset() == HEADER_SIZE
} }
/// Returns whether a complete message has been read
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> { pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?; self.ensure_sufficient_msg_buffer()?;
let msg_size = match self.get_header() { let msg_size = match self.get_header() {
@@ -244,46 +286,55 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(self.message_buffer_avail().len() == msg_size) 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] { pub fn header_buffer_avail(&self) -> &[u8] {
let off = self.header_buffer_offset(); let off = self.header_buffer_offset();
&self.header_buffer()[..off] &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] { pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset(); let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[..off] &mut self.header_buffer_mut()[..off]
} }
/// Returns a slice of the remaining space in the header buffer
pub fn header_buffer_left(&self) -> &[u8] { pub fn header_buffer_left(&self) -> &[u8] {
let off = self.header_buffer_offset(); let off = self.header_buffer_offset();
&self.header_buffer()[off..] &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] { pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset(); let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[off..] &mut self.header_buffer_mut()[off..]
} }
/// Returns a slice of the available data in the message buffer
pub fn message_buffer_avail(&self) -> &[u8] { pub fn message_buffer_avail(&self) -> &[u8] {
let off = self.message_buffer_offset(); let off = self.message_buffer_offset();
&self.message_buffer()[..off] &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] { pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.message_buffer_offset(); let off = self.message_buffer_offset();
&mut self.message_buffer_mut()[..off] &mut self.message_buffer_mut()[..off]
} }
/// Returns a slice of the remaining space in the message buffer
pub fn message_buffer_left(&self) -> &[u8] { pub fn message_buffer_left(&self) -> &[u8] {
let off = self.message_buffer_offset(); let off = self.message_buffer_offset();
&self.message_buffer()[off..] &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] { pub fn message_buffer_left_mut(&mut self) -> &mut [u8] {
let off = self.message_buffer_offset(); let off = self.message_buffer_offset();
&mut self.message_buffer_mut()[off..] &mut self.message_buffer_mut()[off..]
} }
/// Returns the message size from the header if available
pub fn get_header(&self) -> Option<usize> { pub fn get_header(&self) -> Option<usize> {
match self.header_buffer_offset() == HEADER_SIZE { match self.header_buffer_offset() == HEADER_SIZE {
false => None, false => None,
@@ -291,19 +342,23 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
} }
} }
/// Returns the size of the message if header is available
pub fn message_size(&self) -> Option<usize> { pub fn message_size(&self) -> Option<usize> {
self.get_header() self.get_header()
} }
/// Returns the total size of the encoded message including header
pub fn encoded_message_bytes(&self) -> Option<usize> { pub fn encoded_message_bytes(&self) -> Option<usize> {
self.message_size().map(|sz| sz + HEADER_SIZE) self.message_size().map(|sz| sz + HEADER_SIZE)
} }
/// Returns a slice of the message fragment if available
pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> { pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?; self.ensure_sufficient_msg_buffer()?;
Ok(self.message_size().map(|sz| &self.message_buffer()[..sz])) 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<Option<&mut [u8]>, MessageTooLargeError> { pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?; self.ensure_sufficient_msg_buffer()?;
Ok(self Ok(self
@@ -311,12 +366,14 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
.map(|sz| &mut self.message_buffer_mut()[..sz])) .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<Option<&[u8]>, MessageTooLargeError> { pub fn message_fragment_avail(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len(); let off = self.message_buffer_avail().len();
self.message_fragment() self.message_fragment()
.map(|frag| frag.map(|frag| &frag[..off])) .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( pub fn message_fragment_avail_mut(
&mut self, &mut self,
) -> Result<Option<&mut [u8]>, MessageTooLargeError> { ) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
@@ -325,24 +382,28 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
.map(|frag| frag.map(|frag| &mut frag[..off])) .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<Option<&[u8]>, MessageTooLargeError> { pub fn message_fragment_left(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len(); let off = self.message_buffer_avail().len();
self.message_fragment() self.message_fragment()
.map(|frag| frag.map(|frag| &frag[off..])) .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<Option<&mut [u8]>, MessageTooLargeError> { pub fn message_fragment_left_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let off = self.message_buffer_avail().len(); let off = self.message_buffer_avail().len();
self.message_fragment_mut() self.message_fragment_mut()
.map(|frag| frag.map(|frag| &mut frag[off..])) .map(|frag| frag.map(|frag| &mut frag[off..]))
} }
/// Returns a slice of the complete message if available
pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> { pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
let sz = self.message_size(); let sz = self.message_size();
self.message_fragment_avail() self.message_fragment_avail()
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) .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<Option<&mut [u8]>, MessageTooLargeError> { pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let sz = self.message_size(); let sz = self.message_size();
self.message_fragment_avail_mut() self.message_fragment_avail_mut()

View File

@@ -9,46 +9,61 @@ use zeroize::Zeroize;
use crate::{io::IoResultKindHintExt, result::ensure_or}; 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::<u64>(); pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of buffer bounds")] #[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; pub struct PositionOutOfBufferBounds;
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of message bounds")] #[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; pub struct PositionOutOfMessageBounds;
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of header bounds")] #[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; pub struct PositionOutOfHeaderBounds;
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
#[error("Message length is bigger than buffer length")] #[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; pub struct MessageTooLarge;
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
/// Error type for message length sanity checks
pub enum MessageLenSanityError { pub enum MessageLenSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")] #[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
/// Error indicating message length exceeds buffer capacity
#[error("{0:?}")] #[error("{0:?}")]
MessageTooLarge(#[from] MessageTooLarge), MessageTooLarge(#[from] MessageTooLarge),
} }
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
/// Error type for position bounds checking
pub enum PositionSanityError { pub enum PositionSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")] #[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
/// Error indicating position is beyond buffer boundaries
#[error("{0:?}")] #[error("{0:?}")]
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
} }
#[derive(Error, Debug, Clone, Copy)] #[derive(Error, Debug, Clone, Copy)]
/// Error type combining all sanity check errors
pub enum SanityError { pub enum SanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")] #[error("{0:?}")]
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
/// Error indicating position is beyond buffer boundaries
#[error("{0:?}")] #[error("{0:?}")]
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
/// Error indicating message length exceeds buffer capacity
#[error("{0:?}")] #[error("{0:?}")]
MessageTooLarge(#[from] MessageTooLarge), MessageTooLarge(#[from] MessageTooLarge),
} }
@@ -86,12 +101,16 @@ impl From<PositionSanityError> for SanityError {
} }
} }
/// Result of a write operation on an IO stream
pub struct WriteToIoReturn { pub struct WriteToIoReturn {
/// Number of bytes successfully written in this operation
pub bytes_written: usize, pub bytes_written: usize,
/// Whether the write operation has completed fully
pub done: bool, pub done: bool,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
/// Length-prefixed encoder that adds a length header to data before writing
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> { pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
buf: Buf, buf: Buf,
header: [u8; HEADER_SIZE], header: [u8; HEADER_SIZE],
@@ -99,6 +118,7 @@ pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
} }
impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> { impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
/// Creates a new encoder from a buffer
pub fn from_buffer(buf: Buf) -> Self { pub fn from_buffer(buf: Buf) -> Self {
let (header, pos) = ([0u8; HEADER_SIZE], 0); let (header, pos) = ([0u8; HEADER_SIZE], 0);
let mut r = Self { buf, header, pos }; let mut r = Self { buf, header, pos };
@@ -106,6 +126,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
r r
} }
/// Creates a new encoder using the full buffer as a message
pub fn from_message(msg: Buf) -> Self { pub fn from_message(msg: Buf) -> Self {
let mut r = Self::from_buffer(msg); let mut r = Self::from_buffer(msg);
r.restart_write_with_new_message(r.buffer_bytes().len()) r.restart_write_with_new_message(r.buffer_bytes().len())
@@ -113,23 +134,27 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
r r
} }
/// Creates a new encoder using part of the buffer as a message
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> { pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
let mut r = Self::from_message(msg); let mut r = Self::from_message(msg);
r.set_message_len(len)?; r.set_message_len(len)?;
Ok(r) Ok(r)
} }
/// Creates a new encoder from buffer, message length and write position
pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result<Self, SanityError> { pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result<Self, SanityError> {
let mut r = Self::from_buffer(buf); let mut r = Self::from_buffer(buf);
r.set_msg_len_and_position(len, pos)?; r.set_msg_len_and_position(len, pos)?;
Ok(r) Ok(r)
} }
/// Consumes the encoder and returns the underlying buffer
pub fn into_buffer(self) -> Buf { pub fn into_buffer(self) -> Buf {
let Self { buf, .. } = self; let Self { buf, .. } = self;
buf buf
} }
/// Consumes the encoder and returns buffer, message length and write position
pub fn into_parts(self) -> (Buf, usize, usize) { pub fn into_parts(self) -> (Buf, usize, usize) {
let len = self.message_len(); let len = self.message_len();
let pos = self.writing_position(); let pos = self.writing_position();
@@ -137,11 +162,13 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
(buf, len, pos) (buf, len, pos)
} }
/// Resets the encoder state
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.set_msg_len_and_position(0, 0).unwrap(); self.set_msg_len_and_position(0, 0).unwrap();
self.set_message_offset(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<W: io::Write>(&mut self, mut w: W) -> io::Result<()> { pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
use io::ErrorKind as K; use io::ErrorKind as K;
loop { loop {
@@ -158,6 +185,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
} }
} }
/// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> { pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
if self.exhausted() { if self.exhausted() {
return Ok(WriteToIoReturn { return Ok(WriteToIoReturn {
@@ -177,10 +205,12 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}) })
} }
/// Resets write position to start for restarting output
pub fn restart_write(&mut self) { pub fn restart_write(&mut self) {
self.set_writing_position(0).unwrap() 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( pub fn restart_write_with_new_message(
&mut self, &mut self,
len: usize, len: usize,
@@ -189,6 +219,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
.map_err(|e| e.try_into().unwrap()) .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] { pub fn next_slice_to_write(&self) -> &[u8] {
let s = self.header_left(); let s = self.header_left();
if !s.is_empty() { if !s.is_empty() {
@@ -203,66 +234,82 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
&[] &[]
} }
/// Returns true if all data including header and message has been written
pub fn exhausted(&self) -> bool { pub fn exhausted(&self) -> bool {
self.next_slice_to_write().is_empty() self.next_slice_to_write().is_empty()
} }
/// Returns slice containing full message data
pub fn message(&self) -> &[u8] { pub fn message(&self) -> &[u8] {
&self.buffer_bytes()[..self.message_len()] &self.buffer_bytes()[..self.message_len()]
} }
/// Returns slice containing written portion of length header
pub fn header_written(&self) -> &[u8] { pub fn header_written(&self) -> &[u8] {
&self.header()[..self.header_offset()] &self.header()[..self.header_offset()]
} }
/// Returns slice containing unwritten portion of length header
pub fn header_left(&self) -> &[u8] { pub fn header_left(&self) -> &[u8] {
&self.header()[self.header_offset()..] &self.header()[self.header_offset()..]
} }
/// Returns slice containing written portion of message data
pub fn message_written(&self) -> &[u8] { pub fn message_written(&self) -> &[u8] {
&self.message()[..self.message_offset()] &self.message()[..self.message_offset()]
} }
/// Returns slice containing unwritten portion of message data
pub fn message_left(&self) -> &[u8] { pub fn message_left(&self) -> &[u8] {
&self.message()[self.message_offset()..] &self.message()[self.message_offset()..]
} }
/// Returns reference to underlying buffer
pub fn buf(&self) -> &Buf { pub fn buf(&self) -> &Buf {
&self.buf &self.buf
} }
/// Returns slice view of underlying buffer bytes
pub fn buffer_bytes(&self) -> &[u8] { pub fn buffer_bytes(&self) -> &[u8] {
self.buf().borrow() self.buf().borrow()
} }
/// Decodes and returns length header value as u64
pub fn decode_header(&self) -> u64 { pub fn decode_header(&self) -> u64 {
u64::from_le_bytes(self.header) u64::from_le_bytes(self.header)
} }
/// Returns slice containing raw length header bytes
pub fn header(&self) -> &[u8; HEADER_SIZE] { pub fn header(&self) -> &[u8; HEADER_SIZE] {
&self.header &self.header
} }
/// Returns decoded message length from header
pub fn message_len(&self) -> usize { pub fn message_len(&self) -> usize {
self.decode_header() as usize self.decode_header() as usize
} }
/// Returns total encoded size including header and message bytes
pub fn encoded_message_bytes(&self) -> usize { pub fn encoded_message_bytes(&self) -> usize {
self.message_len() + HEADER_SIZE self.message_len() + HEADER_SIZE
} }
/// Returns current write position within header and message
pub fn writing_position(&self) -> usize { pub fn writing_position(&self) -> usize {
self.pos self.pos
} }
/// Returns write offset within length header bytes
pub fn header_offset(&self) -> usize { pub fn header_offset(&self) -> usize {
min(self.writing_position(), HEADER_SIZE) min(self.writing_position(), HEADER_SIZE)
} }
/// Returns write offset within message bytes
pub fn message_offset(&self) -> usize { pub fn message_offset(&self) -> usize {
self.writing_position().saturating_sub(HEADER_SIZE) 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> { pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> {
self.offset_transaction(|t| { self.offset_transaction(|t| {
t.header = header; t.header = header;
@@ -272,14 +319,17 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}) })
} }
/// Encodes and sets length header value with bounds checking
pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> { pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> {
self.set_header(header.to_le_bytes()) self.set_header(header.to_le_bytes())
} }
/// Sets message lengthwith bounds checking
pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> { pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> {
self.encode_and_set_header(len as u64) 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> { pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> {
self.offset_transaction(|t| { self.offset_transaction(|t| {
t.pos = pos; t.pos = pos;
@@ -289,20 +339,24 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}) })
} }
/// Sets write position within header bytes with bounds checking
pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> { pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> {
ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?; ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?;
self.set_writing_position(off).unwrap(); self.set_writing_position(off).unwrap();
Ok(()) Ok(())
} }
/// Sets write position within message bytes with bounds checking
pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> { pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> {
self.set_writing_position(off + HEADER_SIZE) 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> { pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> {
self.set_writing_position(self.writing_position() + off) 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> { pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> {
self.pos = 0; self.pos = 0;
self.set_message_len(len)?; self.set_message_len(len)?;
@@ -347,24 +401,29 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
} }
impl<Buf: BorrowMut<[u8]>> LengthPrefixEncoder<Buf> { impl<Buf: BorrowMut<[u8]>> LengthPrefixEncoder<Buf> {
/// Gets a mutable reference to the underlying buffer
pub fn buf_mut(&mut self) -> &mut Buf { pub fn buf_mut(&mut self) -> &mut Buf {
&mut self.buf &mut self.buf
} }
/// Gets the buffer as mutable bytes
pub fn buffer_bytes_mut(&mut self) -> &mut [u8] { pub fn buffer_bytes_mut(&mut self) -> &mut [u8] {
self.buf.borrow_mut() self.buf.borrow_mut()
} }
/// Gets a mutable reference to the message slice
pub fn message_mut(&mut self) -> &mut [u8] { pub fn message_mut(&mut self) -> &mut [u8] {
let off = self.message_len(); let off = self.message_len();
&mut self.buffer_bytes_mut()[..off] &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] { pub fn message_written_mut(&mut self) -> &mut [u8] {
let off = self.message_offset(); let off = self.message_offset();
&mut self.message_mut()[..off] &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] { pub fn message_left_mut(&mut self) -> &mut [u8] {
let off = self.message_offset(); let off = self.message_offset();
&mut self.message_mut()[off..] &mut self.message_mut()[off..]

View File

@@ -1,2 +1,4 @@
/// Module that handles decoding functionality
pub mod decoder; pub mod decoder;
/// Module that handles encoding functionality
pub mod encoder; pub mod encoder;

View File

@@ -2,19 +2,37 @@
#![warn(clippy::missing_docs_in_private_items)] #![warn(clippy::missing_docs_in_private_items)]
#![recursion_limit = "256"] #![recursion_limit = "256"]
//! Core utility functions and types used across the codebase.
/// Base64 encoding and decoding functionality.
pub mod b64; pub mod b64;
/// Build-time utilities and macros.
pub mod build; pub mod build;
/// Control flow abstractions and utilities.
pub mod controlflow; pub mod controlflow;
/// File descriptor utilities.
pub mod fd; pub mod fd;
/// File system operations and handling.
pub mod file; pub mod file;
/// Functional programming utilities.
pub mod functional; pub mod functional;
/// Input/output operations.
pub mod io; pub mod io;
/// Length prefix encoding schemes implementation.
pub mod length_prefix_encoding; pub mod length_prefix_encoding;
/// Memory manipulation and allocation utilities.
pub mod mem; pub mod mem;
/// MIO integration utilities.
pub mod mio; pub mod mio;
/// Extended Option type functionality.
pub mod option; pub mod option;
/// Extended Result type functionality.
pub mod result; pub mod result;
/// Time and duration utilities.
pub mod time; pub mod time;
/// Type-level numbers and arithmetic.
pub mod typenum; pub mod typenum;
/// Zero-copy serialization utilities.
pub mod zerocopy; pub mod zerocopy;
/// Memory wiping utilities.
pub mod zeroize; pub mod zeroize;

View File

@@ -22,6 +22,7 @@ macro_rules! cat {
} }
// TODO: consistent inout ordering // TODO: consistent inout ordering
/// Copy all bytes from `src` to `dst`. The lengths must match.
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) { pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
dst.borrow_mut().copy_from_slice(src.borrow()); dst.borrow_mut().copy_from_slice(src.borrow());
} }
@@ -41,11 +42,13 @@ pub struct Forgetting<T> {
} }
impl<T> Forgetting<T> { impl<T> Forgetting<T> {
/// Creates a new `Forgetting<T>` instance containing the given value.
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
let value = Some(value); let value = Some(value);
Self { value } Self { value }
} }
/// Extracts and returns the contained value, consuming self.
pub fn extract(mut self) -> T { pub fn extract(mut self) -> T {
let mut value = None; let mut value = None;
swap(&mut value, &mut self.value); swap(&mut value, &mut self.value);
@@ -93,7 +96,9 @@ impl<T> Drop for Forgetting<T> {
} }
} }
/// A trait that provides a method to discard a value without explicitly handling its results.
pub trait DiscardResultExt { pub trait DiscardResultExt {
/// Consumes and discards a value without doing anything with it.
fn discard_result(self); fn discard_result(self);
} }
@@ -101,7 +106,9 @@ impl<T> DiscardResultExt for T {
fn discard_result(self) {} fn discard_result(self) {}
} }
/// Trait that provides a method to explicitly forget values.
pub trait ForgetExt { pub trait ForgetExt {
/// Consumes and forgets a value, preventing its destructor from running.
fn forget(self); fn forget(self);
} }
@@ -111,8 +118,11 @@ impl<T> ForgetExt for T {
} }
} }
/// Extension trait that provides methods for swapping values.
pub trait SwapWithExt { 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; 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); fn swap_with_mut(&mut self, other: &mut Self);
} }
@@ -127,7 +137,9 @@ impl<T> SwapWithExt for T {
} }
} }
/// Extension trait that provides methods for swapping values with default values.
pub trait SwapWithDefaultExt { pub trait SwapWithDefaultExt {
/// Takes the current value and replaces it with the default value, returning the original.
fn swap_with_default(&mut self) -> Self; fn swap_with_default(&mut self) -> Self;
} }
@@ -137,6 +149,7 @@ impl<T: Default> SwapWithDefaultExt for T {
} }
} }
/// Extension trait that provides a method to explicitly move values.
pub trait MoveExt { pub trait MoveExt {
/// Deliberately move the value /// Deliberately move the value
/// ///

View File

@@ -6,14 +6,23 @@ use crate::{
result::OkExt, result::OkExt,
}; };
/// Module containing I/O interest flags for Unix operations
pub mod interest { pub mod interest {
use mio::Interest; use mio::Interest;
/// Interest flag indicating readability
pub const R: Interest = Interest::READABLE; pub const R: Interest = Interest::READABLE;
/// Interest flag indicating writability
pub const W: Interest = Interest::WRITABLE; pub const W: Interest = Interest::WRITABLE;
/// Interest flag indicating both readability and writability
pub const RW: Interest = R.add(W); pub const RW: Interest = R.add(W);
} }
/// Extension trait providing additional functionality for Unix listener
pub trait UnixListenerExt: Sized { pub trait UnixListenerExt: Sized {
/// Creates a new Unix listener by claiming ownership of a raw file descriptor
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>; fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
} }
@@ -27,9 +36,15 @@ impl UnixListenerExt for UnixListener {
} }
} }
/// Extension trait providing additional functionality for Unix streams
pub trait UnixStreamExt: Sized { pub trait UnixStreamExt: Sized {
/// Creates a new Unix stream from an owned file descriptor
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>; fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor and creates a new Unix stream
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>; fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>; fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
} }

View File

@@ -9,6 +9,9 @@ use uds::UnixStreamExt as FdPassingExt;
use crate::fd::{claim_fd_inplace, IntoStdioErr}; 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<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds> pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
where where
Sock: FdPassingExt, Sock: FdPassingExt,
@@ -27,6 +30,8 @@ where
BorrowSock: Borrow<Sock>, BorrowSock: Borrow<Sock>,
BorrowFds: BorrowMut<VecDeque<OwnedFd>>, BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
{ {
/// Creates a new `ReadWithFileDescriptors` by wrapping a socket and a file
/// descriptor queue.
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self { pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
let _sock_dummy = PhantomData; let _sock_dummy = PhantomData;
Self { 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) { pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
let Self { socket, fds, .. } = self; let Self { socket, fds, .. } = self;
(socket, fds) (socket, fds)
} }
/// Returns a reference to the underlying socket.
pub fn socket(&self) -> &Sock { pub fn socket(&self) -> &Sock {
self.socket.borrow() self.socket.borrow()
} }
/// Returns a reference to the file descriptor queue.
pub fn fds(&self) -> &VecDeque<OwnedFd> { pub fn fds(&self) -> &VecDeque<OwnedFd> {
self.fds.borrow() self.fds.borrow()
} }
/// Returns a mutable reference to the file descriptor queue.
pub fn fds_mut(&mut self) -> &mut VecDeque<OwnedFd> { pub fn fds_mut(&mut self) -> &mut VecDeque<OwnedFd> {
self.fds.borrow_mut() self.fds.borrow_mut()
} }
@@ -61,6 +71,7 @@ where
BorrowSock: BorrowMut<Sock>, BorrowSock: BorrowMut<Sock>,
BorrowFds: BorrowMut<VecDeque<OwnedFd>>, BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
{ {
/// Returns a mutable reference to the underlying socket.
pub fn socket_mut(&mut self) -> &mut Sock { pub fn socket_mut(&mut self) -> &mut Sock {
self.socket.borrow_mut() self.socket.borrow_mut()
} }

View File

@@ -10,6 +10,7 @@ use uds::UnixStreamExt as FdPassingExt;
use crate::{repeat, return_if}; use crate::{repeat, return_if};
/// A structure that facilitates writing data and file descriptors to a Unix domain socket
pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds> pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds>
where where
Sock: FdPassingExt, Sock: FdPassingExt,
@@ -30,6 +31,7 @@ where
BorrowSock: Borrow<Sock>, BorrowSock: Borrow<Sock>,
BorrowFds: BorrowMut<VecDeque<Fd>>, BorrowFds: BorrowMut<VecDeque<Fd>>,
{ {
/// Creates a new `WriteWithFileDescriptors` instance with the given socket and file descriptor queue
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self { pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
let _sock_dummy = PhantomData; let _sock_dummy = PhantomData;
let _fd_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) { pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
let Self { socket, fds, .. } = self; let Self { socket, fds, .. } = self;
(socket, fds) (socket, fds)
} }
/// Returns a reference to the underlying socket
pub fn socket(&self) -> &Sock { pub fn socket(&self) -> &Sock {
self.socket.borrow() self.socket.borrow()
} }
/// Returns a reference to the file descriptor queue
pub fn fds(&self) -> &VecDeque<Fd> { pub fn fds(&self) -> &VecDeque<Fd> {
self.fds.borrow() self.fds.borrow()
} }
/// Returns a mutable reference to the file descriptor queue
pub fn fds_mut(&mut self) -> &mut VecDeque<Fd> { pub fn fds_mut(&mut self) -> &mut VecDeque<Fd> {
self.fds.borrow_mut() self.fds.borrow_mut()
} }
@@ -66,6 +72,7 @@ where
BorrowSock: BorrowMut<Sock>, BorrowSock: BorrowMut<Sock>,
BorrowFds: BorrowMut<VecDeque<Fd>>, BorrowFds: BorrowMut<VecDeque<Fd>>,
{ {
/// Returns a mutable reference to the underlying socket
pub fn socket_mut(&mut self) -> &mut Sock { pub fn socket_mut(&mut self) -> &mut Sock {
self.socket.borrow_mut() self.socket.borrow_mut()
} }

View File

@@ -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 { pub trait SomeExt: Sized {
/// Wraps the calling value in `Some()`.
fn some(self) -> Option<Self> { fn some(self) -> Option<Self> {
Some(self) Some(self)
} }

View File

@@ -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<E>: Sized { pub trait OkExt<E>: Sized {
/// Wraps a value in a Result::Ok variant
fn ok(self) -> Result<Self, E>; fn ok(self) -> Result<Self, E>;
} }
@@ -25,6 +27,7 @@ impl<T, E> OkExt<E> for T {
/// ///
/// Implementations must not panic. /// Implementations must not panic.
pub trait GuaranteedValue { pub trait GuaranteedValue {
/// The value type that will be returned by guaranteed()
type Value; type Value;
/// Extract the contained value while being panic-safe, like .unwrap() /// Extract the contained value while being panic-safe, like .unwrap()
@@ -35,7 +38,11 @@ pub trait GuaranteedValue {
fn guaranteed(self) -> Self::Value; fn guaranteed(self) -> Self::Value;
} }
/// Extension trait for adding finally operation to types
pub trait FinallyExt { 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<F: FnOnce(&mut Self)>(self, f: F) -> Self; fn finally<F: FnOnce(&mut Self)>(self, f: F) -> Self;
} }
@@ -125,6 +132,18 @@ impl<T> GuaranteedValue for Guaranteed<T> {
} }
} }
/// 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<E>(b: bool, err: E) -> Result<(), E> { pub fn ensure_or<E>(b: bool, err: E) -> Result<(), E> {
match b { match b {
true => Ok(()), true => Ok(()),
@@ -132,6 +151,18 @@ pub fn ensure_or<E>(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<E>(b: bool, err: E) -> Result<(), E> { pub fn bail_if<E>(b: bool, err: E) -> Result<(), E> {
ensure_or(!b, err) ensure_or(!b, err)
} }

View File

@@ -5,6 +5,16 @@ use std::time::Instant;
/// This is a simple wrapper around `std::time::Instant` that provides a /// This is a simple wrapper around `std::time::Instant` that provides a
/// convenient way to get the seconds elapsed since the creation of the /// convenient way to get the seconds elapsed since the creation of the
/// `Timebase` instance. /// `Timebase` instance.
///
/// # Examples
///
/// ```
/// use rosenpass_util::time::Timebase;
///
/// let timebase = Timebase::default();
/// let now = timebase.now();
/// assert!(now > 0.0);
/// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Timebase(Instant); pub struct Timebase(Instant);

View File

@@ -16,6 +16,7 @@ macro_rules! typenum2const {
/// Trait implemented by constant integers to facilitate conversion to constant integers /// Trait implemented by constant integers to facilitate conversion to constant integers
pub trait IntoConst<T> { pub trait IntoConst<T> {
/// The constant value after conversion
const VALUE: T; const VALUE: T;
} }

View File

@@ -7,56 +7,68 @@ use zeroize::Zeroize;
use crate::zeroize::ZeroizedExt; use crate::zeroize::ZeroizedExt;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
/// A convenience type for working with mutable references to a buffer and an
/// expected target type.
pub struct RefMaker<B: Sized, T> { pub struct RefMaker<B: Sized, T> {
buf: B, buf: B,
_phantom_t: PhantomData<T>, _phantom_t: PhantomData<T>,
} }
impl<B, T> RefMaker<B, T> { impl<B, T> RefMaker<B, T> {
/// Creates a new RefMaker with the given buffer
pub fn new(buf: B) -> Self { pub fn new(buf: B) -> Self {
let _phantom_t = PhantomData; let _phantom_t = PhantomData;
Self { buf, _phantom_t } Self { buf, _phantom_t }
} }
/// Returns the size in bytes needed for target type T
pub const fn target_size() -> usize { pub const fn target_size() -> usize {
std::mem::size_of::<T>() std::mem::size_of::<T>()
} }
/// Consumes this RefMaker and returns the inner buffer
pub fn into_buf(self) -> B { pub fn into_buf(self) -> B {
self.buf self.buf
} }
/// Returns a reference to the inner buffer
pub fn buf(&self) -> &B { pub fn buf(&self) -> &B {
&self.buf &self.buf
} }
/// Returns a mutable reference to the inner buffer
pub fn buf_mut(&mut self) -> &mut B { pub fn buf_mut(&mut self) -> &mut B {
&mut self.buf &mut self.buf
} }
} }
impl<B: ByteSlice, T> RefMaker<B, T> { impl<B: ByteSlice, T> RefMaker<B, T> {
/// Parses the buffer into a reference of type T
pub fn parse(self) -> anyhow::Result<Ref<B, T>> { pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
self.ensure_fit()?; self.ensure_fit()?;
Ref::<B, T>::new(self.buf).context("Parser error!") Ref::<B, T>::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)> { pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> {
self.ensure_fit()?; self.ensure_fit()?;
let (head, tail) = self.buf.split_at(Self::target_size()); let (head, tail) = self.buf.split_at(Self::target_size());
Ok((Self::new(head), tail)) 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)> { pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> {
self.ensure_fit()?; self.ensure_fit()?;
let (head, tail) = self.buf.split_at(Self::target_size()); let (head, tail) = self.buf.split_at(Self::target_size());
Ok((Self::new(head), Self::new(tail))) Ok((Self::new(head), Self::new(tail)))
} }
/// Returns a RefMaker containing only the first `target_size` bytes
pub fn from_prefix(self) -> anyhow::Result<Self> { pub fn from_prefix(self) -> anyhow::Result<Self> {
Ok(Self::from_prefix_with_tail(self)?.0) 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)> { pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> {
self.ensure_fit()?; self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size(); let point = self.bytes().len() - Self::target_size();
@@ -64,6 +76,7 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
Ok((Self::new(tail), head)) 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)> { pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
self.ensure_fit()?; self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size(); let point = self.bytes().len() - Self::target_size();
@@ -71,14 +84,17 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
Ok((Self::new(head), Self::new(tail))) Ok((Self::new(head), Self::new(tail)))
} }
/// Returns a RefMaker containing only the last `target_size` bytes
pub fn from_suffix(self) -> anyhow::Result<Self> { pub fn from_suffix(self) -> anyhow::Result<Self> {
Ok(Self::from_suffix_with_head(self)?.0) Ok(Self::from_suffix_with_head(self)?.0)
} }
/// Returns a reference to the underlying bytes
pub fn bytes(&self) -> &[u8] { pub fn bytes(&self) -> &[u8] {
self.buf().deref() self.buf().deref()
} }
/// Ensures the buffer is large enough to hold type T
pub fn ensure_fit(&self) -> anyhow::Result<()> { pub fn ensure_fit(&self) -> anyhow::Result<()> {
let have = self.bytes().len(); let have = self.bytes().len();
let need = Self::target_size(); let need = Self::target_size();
@@ -91,10 +107,12 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
} }
impl<B: ByteSliceMut, T> RefMaker<B, T> { impl<B: ByteSliceMut, T> RefMaker<B, T> {
/// Creates a zeroed reference of type T from the buffer
pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> { pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> {
self.zeroized().parse() self.zeroized().parse()
} }
/// Returns a mutable reference to the underlying bytes
pub fn bytes_mut(&mut self) -> &mut [u8] { pub fn bytes_mut(&mut self) -> &mut [u8] {
self.buf_mut().deref_mut() self.buf_mut().deref_mut()
} }

View File

@@ -1,10 +1,14 @@
use zerocopy::{ByteSlice, ByteSliceMut, Ref}; use zerocopy::{ByteSlice, ByteSliceMut, Ref};
/// A trait for converting a `Ref<B, T>` into a `Ref<&[u8], T>`.
pub trait ZerocopyEmancipateExt<B, T> { pub trait ZerocopyEmancipateExt<B, T> {
/// Converts this reference into a reference backed by a byte slice.
fn emancipate(&self) -> Ref<&[u8], T>; fn emancipate(&self) -> Ref<&[u8], T>;
} }
/// A trait for converting a `Ref<B, T>` into a mutable `Ref<&mut [u8], T>`.
pub trait ZerocopyEmancipateMutExt<B, T> { pub trait ZerocopyEmancipateMutExt<B, T> {
/// Converts this reference into a mutable reference backed by a byte slice.
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>; fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
} }

View File

@@ -2,19 +2,24 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::RefMaker; use super::RefMaker;
/// Extension trait for zero-copy slice operations.
pub trait ZerocopySliceExt: Sized + ByteSlice { pub trait ZerocopySliceExt: Sized + ByteSlice {
/// Creates a new `RefMaker` for the given slice.
fn zk_ref_maker<T>(self) -> RefMaker<Self, T> { fn zk_ref_maker<T>(self) -> RefMaker<Self, T> {
RefMaker::<Self, T>::new(self) RefMaker::<Self, T>::new(self)
} }
/// Parses the slice into a zero-copy reference.
fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().parse() self.zk_ref_maker().parse()
} }
/// Parses a prefix of the slice into a zero-copy reference.
fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_prefix()?.parse() self.zk_ref_maker().from_prefix()?.parse()
} }
/// Parses a suffix of the slice into a zero-copy reference.
fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_suffix()?.parse() self.zk_ref_maker().from_suffix()?.parse()
} }
@@ -22,15 +27,19 @@ pub trait ZerocopySliceExt: Sized + ByteSlice {
impl<B: ByteSlice> ZerocopySliceExt for B {} impl<B: ByteSlice> ZerocopySliceExt for B {}
/// Extension trait for zero-copy slice operations with mutable slices.
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut { pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
/// Creates a new zeroed reference from the entire slice.
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().make_zeroized() self.zk_ref_maker().make_zeroized()
} }
/// Creates a new zeroed reference from a prefix of the slice.
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_prefix()?.make_zeroized() self.zk_ref_maker().from_prefix()?.make_zeroized()
} }
/// Creates a new zeroed reference from a suffix of the slice.
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> { fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_suffix()?.make_zeroized() self.zk_ref_maker().from_suffix()?.make_zeroized()
} }

View File

@@ -1,6 +1,22 @@
use zeroize::Zeroize; 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 { pub trait ZeroizedExt: Zeroize + Sized {
/// Zeroizes the value in place and returns self
fn zeroized(mut self) -> Self { fn zeroized(mut self) -> Self {
self.zeroize(); self.zeroize();
self self

View File

@@ -28,7 +28,7 @@ derive_builder = { workspace = true }
postcard = { 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 # 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? # 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 } libc = { version = "0.2", optional = true }
# Mio broker client # Mio broker client