mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
30 Commits
dev/karo/d
...
dev/karo/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b556e317 | ||
|
|
53168dc62d | ||
|
|
2cfe703118 | ||
|
|
a2d7c3aaa6 | ||
|
|
1aa111570e | ||
|
|
a91d61f9f0 | ||
|
|
ff7827c24e | ||
|
|
255e377d29 | ||
|
|
50505d81cc | ||
|
|
10484cc6d4 | ||
|
|
d27e602f43 | ||
|
|
a279dfc0b1 | ||
|
|
caf2f6bfec | ||
|
|
d398ad369e | ||
|
|
00696321ff | ||
|
|
d807a1bca7 | ||
|
|
4daf97b2ee | ||
|
|
b394e302ab | ||
|
|
198bc2d5f2 | ||
|
|
fc2f535eae | ||
|
|
302e249f08 | ||
|
|
d8fe3eba5f | ||
|
|
61b8b28e86 | ||
|
|
26f77924f8 | ||
|
|
2e0e2cfa0c | ||
|
|
a537eb3e1b | ||
|
|
ea233bf137 | ||
|
|
db8796ab40 | ||
|
|
0353c82729 | ||
|
|
51d4dede15 |
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -403,9 +403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.38"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
|
||||
checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
|
||||
dependencies = [
|
||||
"clap 4.5.23",
|
||||
]
|
||||
|
||||
@@ -49,7 +49,7 @@ typenum = "1.17.0"
|
||||
log = { version = "0.4.22" }
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
clap_mangen = "0.2.24"
|
||||
clap_complete = "4.5.38"
|
||||
clap_complete = "4.5.40"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
|
||||
|
||||
@@ -13,7 +13,6 @@ pub use hash::KEY_LEN;
|
||||
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
|
||||
/// # fn do_doc_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// // create use once hash domain for the protocol identifier
|
||||
/// let mut hash_domain = HashDomain::zero();
|
||||
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
|
||||
@@ -31,10 +30,7 @@ pub use hash::KEY_LEN;
|
||||
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
|
||||
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # do_doc_test().unwrap();
|
||||
///
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
///```
|
||||
///
|
||||
|
||||
|
||||
@@ -20,3 +20,6 @@ memsec = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -2,14 +2,29 @@
|
||||
|
||||
use core::ptr;
|
||||
|
||||
/// Little endian memcmp version of quinier/memsec
|
||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
||||
/// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30)
|
||||
///
|
||||
/// # Panic & Safety
|
||||
///
|
||||
/// Both input arrays must be at least of the indicated length.
|
||||
///
|
||||
/// See [std::ptr::read_volatile] on safety.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let a = [1, 2, 3, 4];
|
||||
/// let b = [1, 2, 3, 4];
|
||||
/// let c = [1, 2, 2, 5];
|
||||
/// let d = [1, 2, 2, 4];
|
||||
///
|
||||
/// unsafe {
|
||||
/// use rosenpass_constant_time::memcmp_le;
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(never)]
|
||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
let mut res = 0;
|
||||
@@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compare::memcmp_le;
|
||||
|
||||
#[test]
|
||||
fn memcmp_le_test() {
|
||||
let a = [1, 2, 3, 4];
|
||||
let b = [1, 2, 3, 4];
|
||||
let c = [1, 2, 2, 5];
|
||||
let d = [1, 2, 2, 4];
|
||||
|
||||
unsafe {
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,16 @@ use core::hint::black_box;
|
||||
/// and increment that integer.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - The number of carry operations that occur may affect timing slightly
|
||||
/// - Memory access patterns are sequential and predictable
|
||||
///
|
||||
/// The carry operation timing variation is mitigated through the use of black_box,
|
||||
/// but the linear scaling with input size is inherent to the operation.
|
||||
/// These timing characteristics are generally considered acceptable for most
|
||||
/// cryptographic counter implementations.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
@@ -7,6 +7,32 @@
|
||||
//! ## TODO
|
||||
//! Figure out methodology to ensure that code is actually constant time, see
|
||||
//! <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rosenpass_constant_time::{memcmp, compare};
|
||||
//!
|
||||
//! let a = [1, 2, 3, 4];
|
||||
//! let b = [1, 2, 3, 4];
|
||||
//! let c = [1, 2, 3, 5];
|
||||
//!
|
||||
//! // Compare for equality
|
||||
//! assert!(memcmp(&a, &b));
|
||||
//! assert!(!memcmp(&a, &c));
|
||||
//!
|
||||
//! // Compare lexicographically
|
||||
//! assert_eq!(compare(&a, &c), -1); // a < c
|
||||
//! assert_eq!(compare(&c, &a), 1); // c > a
|
||||
//! assert_eq!(compare(&a, &b), 0); // a == b
|
||||
//! ```
|
||||
//!
|
||||
//! # Security Notes
|
||||
//!
|
||||
//! While these functions aim to be constant-time, they may leak timing information in some cases:
|
||||
//!
|
||||
//! - Length mismatches between inputs are immediately detectable
|
||||
//! - Execution time scales linearly with input size
|
||||
|
||||
mod compare;
|
||||
mod increment;
|
||||
@@ -14,6 +40,7 @@ mod memcmp;
|
||||
mod xor;
|
||||
|
||||
pub use compare::compare;
|
||||
pub use compare::memcmp_le;
|
||||
pub use increment::increment;
|
||||
pub use memcmp::memcmp;
|
||||
pub use xor::xor;
|
||||
|
||||
@@ -113,9 +113,10 @@ mod tests {
|
||||
// Pearson correlation
|
||||
let correlation = cv / (sd_x * sd_y);
|
||||
println!("correlation: {:.6?}", correlation);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
correlation.abs() < 0.01,
|
||||
"execution time correlates with result"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To};
|
||||
|
||||
/// Xors the source into the destination
|
||||
///
|
||||
/// Performs a constant-time XOR operation between two byte slices
|
||||
///
|
||||
/// Takes a source slice and XORs it with the destination slice in-place using the
|
||||
/// rosenpass_to trait for destination management.
|
||||
///
|
||||
/// # Panics
|
||||
/// If source and destination are of different sizes.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - Length mismatches between source and destination are immediately detectable via panic
|
||||
/// - Memory access patterns follow a predictable sequential pattern
|
||||
///
|
||||
/// These leaks are generally considered acceptable in most cryptographic contexts
|
||||
/// as they don't reveal information about the actual content being XORed.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
@@ -91,3 +91,6 @@ experiment_api = [
|
||||
internal_signal_handling_for_coverage_reports = ["signal-hook"]
|
||||
internal_testing = []
|
||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
|
||||
pub struct ApiConfig {
|
||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||
/// connections on
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Contains the code used to parse command line parameters for rosenpass.
|
||||
//!
|
||||
//! [CliArgs::run] is called by the rosenpass main function and contains the
|
||||
//! bulk of our boostrapping code while the main function just sets up the basic environment
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use clap::{Parser, Subcommand};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
@@ -31,15 +36,25 @@ use {
|
||||
std::thread,
|
||||
};
|
||||
|
||||
/// enum representing a choice of interface to a WireGuard broker
|
||||
/// How to reach a WireGuard PSK Broker
|
||||
#[derive(Debug)]
|
||||
pub enum BrokerInterface {
|
||||
/// The PSK Broker is listening on a unix socket at the given path
|
||||
Socket(PathBuf),
|
||||
/// The PSK Broker broker is already connected to this process; a
|
||||
/// unix socket stream can be reached at the given file descriptor.
|
||||
///
|
||||
/// This is generally used with file descriptor passing.
|
||||
FileDescriptor(i32),
|
||||
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
|
||||
/// and hand one end of the socketpair to the broker process via file
|
||||
/// descriptor passing to the subprocess
|
||||
SocketPair,
|
||||
}
|
||||
|
||||
/// struct holding all CLI arguments for `clap` crate to parse
|
||||
/// Command line arguments to the Rosenpass binary.
|
||||
///
|
||||
/// Used for parsing with [clap].
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||
pub struct CliArgs {
|
||||
@@ -80,6 +95,7 @@ pub struct CliArgs {
|
||||
#[arg(short, long, group = "psk-broker-specs")]
|
||||
psk_broker_spawn: bool,
|
||||
|
||||
/// The subcommand to be invoked
|
||||
#[command(subcommand)]
|
||||
pub command: Option<CliCommand>,
|
||||
|
||||
@@ -98,6 +114,10 @@ pub struct CliArgs {
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
/// Apply the command line parameters to the Rosenpass configuration struct
|
||||
///
|
||||
/// Generally the flow of control here is that all the command line parameters
|
||||
/// are merged into the configuration file to avoid much code duplication.
|
||||
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_config(_cfg)?;
|
||||
@@ -123,9 +143,11 @@ impl CliArgs {
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||
@@ -138,9 +160,10 @@ impl CliArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
None
|
||||
}
|
||||
@@ -244,15 +267,17 @@ pub enum CliCommand {
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
/// Runs the command specified via CLI
|
||||
/// Run Rosenpass with the given command line parameters
|
||||
///
|
||||
/// ## TODO
|
||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
/// This contains the bulk of our startup logic with
|
||||
/// the main function just setting up the basic environment
|
||||
/// and then calling this function.
|
||||
pub fn run(
|
||||
self,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
use CliCommand::*;
|
||||
match &self.command {
|
||||
Some(GenConfig { config_file, force }) => {
|
||||
@@ -403,6 +428,7 @@ impl CliArgs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||
fn event_loop(
|
||||
config: config::Rosenpass,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
@@ -470,6 +496,19 @@ impl CliArgs {
|
||||
srv.event_loop()
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn create_broker(
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
@@ -485,6 +524,19 @@ impl CliArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
fn create_broker(
|
||||
_broker_interface: Option<BrokerInterface>,
|
||||
@@ -492,6 +544,10 @@ impl CliArgs {
|
||||
Ok(Box::new(NativeUnixBroker::new()))
|
||||
}
|
||||
|
||||
/// Used by [Self::create_broker] if the `experiment_api` is configured
|
||||
/// to set up the connection with the PSK broker process as configured
|
||||
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||
// Connect to the psk broker unix socket if one was specified
|
||||
@@ -549,7 +605,7 @@ impl CliArgs {
|
||||
}
|
||||
|
||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
//! [`Rosenpass`] which holds such a configuration.
|
||||
//!
|
||||
//! ## TODO
|
||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||
|
||||
use crate::protocol::{SPk, SSk};
|
||||
use rosenpass_util::file::LoadValue;
|
||||
use std::{
|
||||
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
/// Configuration for the Rosenpass key exchange
|
||||
///
|
||||
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Rosenpass {
|
||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||
@@ -46,7 +50,10 @@ pub struct Rosenpass {
|
||||
/// list of [`SocketAddr`] to listen on
|
||||
///
|
||||
/// Examples:
|
||||
/// - `0.0.0.0:123`
|
||||
///
|
||||
/// - `0.0.0.0:123` – Listen on any interface using IPv4, port 123
|
||||
/// - `[::1]:1234` – Listen on IPv6 localhost, port 1234
|
||||
/// - `[::]:4476` – Listen on any IPv4 or IPv6 interface, port 4476
|
||||
pub listen: Vec<SocketAddr>,
|
||||
|
||||
/// log verbosity
|
||||
@@ -68,6 +75,7 @@ pub struct Rosenpass {
|
||||
pub config_file_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Public key and secret key locations.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct Keypair {
|
||||
/// path to the public key file
|
||||
@@ -78,6 +86,7 @@ pub struct Keypair {
|
||||
}
|
||||
|
||||
impl Keypair {
|
||||
/// Construct a keypair from its fields
|
||||
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
||||
let public_key = public_key.as_ref().to_path_buf();
|
||||
let secret_key = secret_key.as_ref().to_path_buf();
|
||||
@@ -88,62 +97,72 @@ impl Keypair {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
/// Level of verbosity for [crate::app_server::AppServer]
|
||||
///
|
||||
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
|
||||
/// for details.
|
||||
///
|
||||
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum Verbosity {
|
||||
Quiet,
|
||||
Verbose,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - examples
|
||||
/// - documentation
|
||||
/// Configuration data for a single Rosenpass peer
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RosenpassPeer {
|
||||
/// path to the public key of the peer
|
||||
pub public_key: PathBuf,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// The hostname and port to connect to
|
||||
///
|
||||
/// Can be a
|
||||
///
|
||||
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
|
||||
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
|
||||
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
|
||||
pub endpoint: Option<String>,
|
||||
|
||||
/// path to the pre-shared key with the peer
|
||||
/// path to the pre-shared key shared with the peer
|
||||
///
|
||||
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
|
||||
pub pre_shared_key: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||
/// to the given file and write a notification to standard out to let the calling application
|
||||
/// know that a new key was exchanged
|
||||
#[serde(default)]
|
||||
pub key_out: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[serde(flatten)]
|
||||
pub wg: Option<WireGuard>,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct WireGuard {
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||
/// key exchange
|
||||
pub device: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||
pub peer: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Extra parameters passed to the `wg` command
|
||||
#[serde(default)]
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Rosenpass {
|
||||
/// Generate an empty configuration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
@@ -156,8 +175,15 @@ impl Rosenpass {
|
||||
/// checked whether they even exist.
|
||||
///
|
||||
/// ## TODO
|
||||
///
|
||||
/// - consider using a different algorithm to determine home directory – the below one may
|
||||
/// behave unexpectedly on Windows
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||
// read file and deserialize
|
||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||
@@ -185,7 +211,13 @@ impl Rosenpass {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Write a config to a file
|
||||
/// Encode a configuration object as toml and write it to a file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||
let serialized_config =
|
||||
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||
@@ -194,6 +226,12 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Commit the configuration to where it came from, overwriting the original file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn commit(&self) -> anyhow::Result<()> {
|
||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||
@@ -201,13 +239,21 @@ impl Rosenpass {
|
||||
self.store(&self.config_file_path)
|
||||
}
|
||||
|
||||
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
|
||||
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_app_server(_srv)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a configuration
|
||||
/// Check that the configuration is sound, ensuring
|
||||
/// for instance that the referenced files exist
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
if let Some(ref keypair) = self.keypair {
|
||||
// check the public key file exists
|
||||
@@ -284,6 +330,21 @@ impl Rosenpass {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
|
||||
/// and the configuration values.
|
||||
///
|
||||
/// This was introduced when we introduced a unix-socket API feature allowing the server
|
||||
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
|
||||
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
|
||||
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
|
||||
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
|
||||
/// enabled in the configuration.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||
@@ -299,15 +360,38 @@ impl Rosenpass {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Produce an empty confuguration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
|
||||
/// Produce configuration from the keypair
|
||||
///
|
||||
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||
Self::new(Some(Keypair::new(pk, sk)))
|
||||
}
|
||||
|
||||
/// Creates a new configuration
|
||||
/// Initialize a minimal configuration with the [Self::keypair] field supplied
|
||||
/// as a parameter
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||
Self {
|
||||
keypair,
|
||||
@@ -321,6 +405,14 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
||||
///
|
||||
/// I.e. listen on any interface.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn add_if_any(&mut self, port: u16) {
|
||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||
@@ -333,8 +425,20 @@ impl Rosenpass {
|
||||
self.listen.push(ipv6_any);
|
||||
}
|
||||
|
||||
/// from chaotic args
|
||||
/// Quest: the grammar is undecideable, what do we do here?
|
||||
/// Parser for the old, IP style grammar.
|
||||
///
|
||||
/// See out manual page rosenpass-exchange(1) on details about the grammar.
|
||||
///
|
||||
/// This function parses the grammar and turns it into an instance of the configuration
|
||||
/// struct.
|
||||
///
|
||||
/// TODO: the grammar is undecidable, what do we do here?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||
|
||||
@@ -525,11 +629,13 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
impl Default for Verbosity {
|
||||
/// Self::Quiet
|
||||
fn default() -> Self {
|
||||
Self::Quiet
|
||||
}
|
||||
}
|
||||
|
||||
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
|
||||
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||
secret_key = "/path/to/rp-secret-key"
|
||||
listen = []
|
||||
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use std::{borrow::Borrow, net::IpAddr};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||
toml::from_str(s.borrow())
|
||||
@@ -664,37 +770,6 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_cli_parse() {
|
||||
let args = split_str(
|
||||
"public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out",
|
||||
);
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_parse_multiple_peers() {
|
||||
let args = split_str(
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Drop for KillChild {
|
||||
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||
// so the signal is absorbed
|
||||
loop {
|
||||
rustix::process::kill_process(pid, Term).discard_result();
|
||||
kill_process(pid, Term).discard_result();
|
||||
if self.0.try_wait().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use rosenpass::config::Rosenpass;
|
||||
|
||||
#[test]
|
||||
fn config_Rosenpass_add_if_any_example() {
|
||||
let mut v = Rosenpass::empty();
|
||||
v.add_if_any(4000);
|
||||
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
|
||||
}
|
||||
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use rosenpass::config::{Keypair, Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_new() {
|
||||
let (sk, pk) = ("./example.sk", "./example.pk");
|
||||
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::default());
|
||||
|
||||
assert_eq!(
|
||||
Rosenpass::from_sk_pk(sk, pk),
|
||||
Rosenpass::new(Some(Keypair::new(pk, sk)))
|
||||
);
|
||||
|
||||
let mut v = Rosenpass::empty();
|
||||
v.keypair = Some(Keypair::new(pk, sk));
|
||||
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
|
||||
}
|
||||
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn parse_simple() {
|
||||
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out";
|
||||
let argv = argv.split(' ').map(|s| s.to_string()).collect();
|
||||
|
||||
let config = Rosenpass::parse_args(argv).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
);
|
||||
}
|
||||
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rosenpass::config::{Rosenpass, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_store() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = tmpdir.path().join("config.toml");
|
||||
|
||||
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Can not commit config, path not known
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can store it to an explicit path though
|
||||
c.store(&cfg)?;
|
||||
|
||||
// Storing does not set commitment path
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can reload the config now and the configurations
|
||||
// are equal if we adjust the commitment path
|
||||
let mut c2 = Rosenpass::load(&cfg)?;
|
||||
c.config_file_path = PathBuf::from(&cfg);
|
||||
assert_eq!(c, c2);
|
||||
|
||||
// And this loaded config can now be committed
|
||||
c2.verbosity = Verbosity::Verbose;
|
||||
c2.commit()?;
|
||||
|
||||
// And the changes actually made it to disk
|
||||
let c3 = Rosenpass::load(cfg)?;
|
||||
assert_eq!(c2, c3);
|
||||
assert_ne!(c, c3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fs;
|
||||
|
||||
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
// Empty validates OK
|
||||
assert!(Rosenpass::empty().validate().is_ok());
|
||||
|
||||
// Missing secret key does not pass usefulness
|
||||
assert!(Rosenpass::empty().check_usefullness().is_err());
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Missing secret key does not validate
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But passes usefulness (the configuration is useful but invalid)
|
||||
assert!(cfg.check_usefullness().is_ok());
|
||||
|
||||
// Providing empty key files does not help
|
||||
fs::write(&sk, b"")?;
|
||||
fs::write(&pk, b"")?;
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But after providing proper key files, the configuration validates
|
||||
generate_and_save_keypair(sk, pk)?;
|
||||
assert!(cfg.validate().is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -160,6 +160,9 @@ fn check_example_config() {
|
||||
.output()
|
||||
.expect("EXAMPLE_CONFIG not valid");
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(stderr, "");
|
||||
|
||||
fs::copy(
|
||||
tmp_dir.path().join("rp-public-key"),
|
||||
tmp_dir.path().join("rp-peer-public-key"),
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
collections::VecDeque,
|
||||
fmt::{Debug, Write},
|
||||
ops::{DerefMut, RangeBounds},
|
||||
ops::DerefMut,
|
||||
};
|
||||
|
||||
use rand::distributions::uniform::SampleBorrow;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_util::result::OkExt;
|
||||
@@ -28,48 +26,53 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
|
||||
sim.poll_loop(150)?; // Poll 75 times
|
||||
let transcript = sim.transcript;
|
||||
|
||||
let completions: Vec<_> = transcript
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!completions.is_empty(),
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
completions[0].0 < 20.0,
|
||||
_completions[0].0 < 20.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
completions.len() >= 3,
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&completions[1].0),
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
|
||||
#[cfg(not(coverage))]
|
||||
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
@@ -106,48 +109,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
let transcript = sim.transcript;
|
||||
let completions: Vec<_> = transcript
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!completions.is_empty(),
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
completions[0].0 < 10.0,
|
||||
_completions[0].0 < 10.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
completions.len() >= 3,
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&completions[1].0),
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
assert!((110.0..175.0).contains(&(completions[2].0 - completions[1].0)), "\
|
||||
#[cfg(not(coverage))]
|
||||
assert!((110.0..175.0).contains(&(_completions[2].0 - _completions[1].0)), "\
|
||||
First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {completions:?}\
|
||||
Completions: {_completions:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MallocVec] and [MallocBox] types.
|
||||
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MallocAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MallocAllocator {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
|
||||
|
||||
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
|
||||
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box(data);
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// ```
|
||||
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
|
||||
MallocBox::<T>::new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
|
||||
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
|
||||
/// malloc_vec.push(0u8);
|
||||
/// malloc_vec.push(1u8);
|
||||
/// malloc_vec.push(2u8);
|
||||
/// # let mut element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn malloc_vec<T>() -> MallocVec<T> {
|
||||
MallocVec::<T>::new_in(MallocAllocator::new())
|
||||
}
|
||||
|
||||
impl MallocAllocator {
|
||||
/// Creates a new [MallocAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
@@ -94,6 +147,9 @@ mod test {
|
||||
malloc_allocation_impl::<8>(&alloc);
|
||||
malloc_allocation_impl::<64>(&alloc);
|
||||
malloc_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
|
||||
|
||||
#![cfg(target_os = "linux")]
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemfdSecAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MemfdSecAllocator {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
|
||||
|
||||
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
|
||||
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// ```
|
||||
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
|
||||
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
|
||||
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
|
||||
/// memfdsec_vec.push(0u8);
|
||||
/// memfdsec_vec.push(1u8);
|
||||
/// memfdsec_vec.push(2u8);
|
||||
/// # let mut element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
|
||||
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
impl MemfdSecAllocator {
|
||||
/// Create a new [MemfdSecAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
@@ -95,6 +148,9 @@ mod test {
|
||||
memfdsec_allocation_impl::<8>(&alloc);
|
||||
memfdsec_allocation_impl::<64>(&alloc);
|
||||
memfdsec_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
//! This module provides wrappers around the memfdsec and the memsec allocators from the
|
||||
//! [memsec] crate. The wrappers implement the [Allocator](allocator_api2::alloc::Allocator) trait
|
||||
//! and can thus be used as a drop in replacement wherever ever this trait is required.
|
||||
|
||||
pub mod malloc;
|
||||
pub mod memfdsec;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This module provides a [SecretAllocator](SecretAlloc) that allocates memory with extra
|
||||
//! protections that make it more difficult for threat actors to access secrets that they aren't
|
||||
//! authorized to access. At the moment the `memsec` and `memfdsec` allocators from the
|
||||
//! [memsec] crate are supported for this purpose.
|
||||
//!
|
||||
//! [SecretAlloc] implements the [Allocator] trait and can thus be used as a drop in replacement
|
||||
//! wherever ever this trait is required.
|
||||
//!
|
||||
//! The module also provides the [SecretVec] and [SecretBox] types.
|
||||
pub mod memsec;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
@@ -8,15 +17,50 @@ use memsec::malloc::MallocAllocator;
|
||||
#[cfg(target_os = "linux")]
|
||||
use memsec::memfdsec::MemfdSecAllocator;
|
||||
|
||||
/// Globally configures which [SecretAllocType] to use as default for
|
||||
/// [SecretAllocators](SecretAlloc).
|
||||
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
|
||||
|
||||
/// Sets the secret allocation type to use.
|
||||
/// Intended usage at startup before secret allocation
|
||||
/// takes place
|
||||
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
|
||||
/// It is intended that this function is called at startup before a secret allocation
|
||||
/// takes place.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use std::alloc::Layout;
|
||||
/// # use allocator_api2::alloc::Allocator;
|
||||
/// # use rosenpass_secret_memory::alloc::{set_secret_alloc_type, SecretAlloc, SecretAllocType};
|
||||
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// let secret_alloc = SecretAlloc::default();
|
||||
/// unsafe {
|
||||
/// let memory = secret_alloc.allocate(Layout::from_size_align_unchecked(128, 32))?;
|
||||
/// }
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
/// ```
|
||||
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
|
||||
ALLOC_TYPE.set(alloc_type).unwrap();
|
||||
}
|
||||
|
||||
/// Initializes type of allocator to be sued with `alloc_type` if it is not initialized yet. Returns
|
||||
/// the current [SecretAllocType] afterward.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use std::alloc::Layout;
|
||||
/// # use allocator_api2::alloc::Allocator;
|
||||
/// # use rosenpass_secret_memory::alloc::{get_or_init_secret_alloc_type, set_secret_alloc_type,
|
||||
/// # SecretAlloc, SecretAllocType};
|
||||
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// #[cfg(target_os = "linux")] {
|
||||
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMemfdSec);
|
||||
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
|
||||
/// }
|
||||
/// #[cfg(not(target_os = "linux"))] {
|
||||
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
|
||||
/// }
|
||||
///```
|
||||
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
|
||||
*ALLOC_TYPE.get_or_init(|| alloc_type)
|
||||
}
|
||||
@@ -28,6 +72,7 @@ pub enum SecretAllocType {
|
||||
MemsecMemfdSec,
|
||||
}
|
||||
|
||||
/// An [Allocator] that uses a [SecretAllocType] for allocation.
|
||||
pub struct SecretAlloc {
|
||||
alloc_type: SecretAllocType,
|
||||
}
|
||||
@@ -68,19 +113,72 @@ unsafe impl Allocator for SecretAlloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [allocator_api2::boxed::Box] that is backed by [SecretAlloc].
|
||||
pub type SecretBox<T> = allocator_api2::boxed::Box<T, SecretAlloc>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
|
||||
pub type SecretVec<T> = allocator_api2::vec::Vec<T, SecretAlloc>;
|
||||
|
||||
/// Try to allocate a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_box_try, SecretBox};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let data: u8 = 42;
|
||||
/// let secret_box: SecretBox<u8> = secret_box_try(data)?;
|
||||
/// # assert_eq!(*secret_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
|
||||
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
|
||||
}
|
||||
|
||||
/// Allocates a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_box, SecretBox};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let data: u8 = 42;
|
||||
/// let secret_box: SecretBox<u8> = secret_box(data);
|
||||
/// # assert_eq!(*secret_box, 42u8);
|
||||
/// ```
|
||||
pub fn secret_box<T>(x: T) -> SecretBox<T> {
|
||||
SecretBox::<T>::new_in(x, SecretAlloc::default())
|
||||
}
|
||||
|
||||
/// Allocate a [SecretVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_vec, SecretVec};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let mut secret_vec: SecretVec<u8> = secret_vec();
|
||||
/// secret_vec.push(0u8);
|
||||
/// secret_vec.push(1u8);
|
||||
/// secret_vec.push(2u8);
|
||||
/// # let mut element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn secret_vec<T>() -> SecretVec<T> {
|
||||
SecretVec::<T>::new_in(SecretAlloc::default())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
//! This module provides a helper for creating debug prints for byte slices.
|
||||
//! See [debug_crypto_array] for more details.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::fmt::{Debug, Formatter};
|
||||
/// use rosenpass_secret_memory::debug::debug_crypto_array;
|
||||
///
|
||||
/// struct U8Wrapper {
|
||||
/// pub u_eigt: Vec<u8>
|
||||
/// }
|
||||
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
|
||||
/// debug_crypto_array(self.u_eigt.as_slice(), f)
|
||||
/// }
|
||||
/// }
|
||||
/// // Short byte slices are printed completely.
|
||||
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
|
||||
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
|
||||
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
|
||||
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
|
||||
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
|
||||
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
/// ```
|
||||
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("[{}]=")?;
|
||||
if v.len() > 64 {
|
||||
|
||||
@@ -1,8 +1,46 @@
|
||||
//! Objects that implement this Trait provide a way to store their data in way that respects the
|
||||
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
|
||||
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
|
||||
//! equivalent to [rosenpass_util::file::Visibility::Secret].
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
|
||||
/// be implemented as follows for example:
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::io::Write;
|
||||
/// use std::path::Path;
|
||||
/// use rosenpass_secret_memory::file::StoreSecret;
|
||||
///
|
||||
/// use rosenpass_util::file::{fopen_w, Visibility};
|
||||
///
|
||||
/// struct MyWeirdI32 {
|
||||
/// _priv_i32: [u8; 4],
|
||||
/// }
|
||||
///
|
||||
/// impl StoreSecret for MyWeirdI32 {
|
||||
/// type Error = std::io::Error;
|
||||
///
|
||||
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
|
||||
/// [rosenpass_util::file::Visibility::Secret].
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
|
||||
/// Stores the object. No requirement on the visibility is given, but it is common to store
|
||||
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
//! This library provides functionality for working with secret data and protecting it in
|
||||
//! memory from illegitimate access.
|
||||
//!
|
||||
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
|
||||
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
|
||||
//! We refer to the documentation of these modules for more details on their appropriate usage.
|
||||
//!
|
||||
//! The [policy] module then provides functionality for specifying which of the allocators from
|
||||
//! the [alloc] module should be used.
|
||||
//!
|
||||
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
|
||||
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
|
||||
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
|
||||
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
|
||||
//! # Exmaple
|
||||
//! ```rust
|
||||
//! use zeroize::Zeroize;
|
||||
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
|
||||
//! secret_policy_try_use_memfd_secrets();
|
||||
//! let mut my_secret: Secret<32> = Secret::random();
|
||||
//! my_secret.zeroize();
|
||||
//! ```
|
||||
//!
|
||||
//! # Futher functionality
|
||||
//! In addition to this core functionality, this library provides some more smaller tools.
|
||||
//!
|
||||
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
|
||||
//! that of [Secret].
|
||||
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
|
||||
//! backed by byte arrays or slices, like for example [Secret].
|
||||
//! 3. The [mod@file] module provides functionality to store [Secrets](crate::Secret)
|
||||
//! and [Public] in files such that the file's [Visibility](rosenpass_util::file::Visibility)
|
||||
//! corresponds to the confidentiality of the data.
|
||||
//! 4. The [rand] module provides a simple way of generating randomness.
|
||||
|
||||
pub mod debug;
|
||||
pub mod file;
|
||||
pub mod rand;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
|
||||
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
|
||||
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
|
||||
//! This module provides functionality for just that.
|
||||
|
||||
/// This function sets the `memfdsec` allocator as the default in case it is supported by
|
||||
/// the target and uses the `memsec` allocator otherwise.
|
||||
///
|
||||
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
|
||||
pub fn secret_policy_try_use_memfd_secrets() {
|
||||
let alloc_type = {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
|
||||
/// this is only supported on Linux targets.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn secret_policy_use_only_memfd_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
|
||||
@@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
|
||||
pub fn secret_policy_use_only_malloc_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
|
||||
/// Contains information in the form of a byte array that may be known to the
|
||||
/// public
|
||||
/// public.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
// TODO: We should get rid of the Public type; just use a normal value
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
|
||||
}
|
||||
|
||||
impl<const N: usize> Public<N> {
|
||||
/// Create a new [Public] from a byte slice
|
||||
/// Create a new [Public] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
copy_slice(value).to_this(Self::zero)
|
||||
}
|
||||
|
||||
/// Create a new [Public] from a byte array
|
||||
/// Create a new [Public] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Create a zero initialized [Public]
|
||||
/// Create a zero initialized [Public].
|
||||
pub fn zero() -> Self {
|
||||
Self { value: [0u8; N] }
|
||||
}
|
||||
|
||||
/// Create a random initialized [Public]
|
||||
/// Create a random initialized [Public].
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [Public]
|
||||
/// Randomize all bytes in an existing [Public].
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.value.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&self.value, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.value
|
||||
}
|
||||
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
impl<const N: usize> LoadValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
|
||||
impl<const N: usize> StoreValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, **self)?;
|
||||
Ok(())
|
||||
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let p = path.as_ref();
|
||||
let mut f = [0u8; F];
|
||||
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
mut writer: W,
|
||||
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public, PublicBox};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
|
||||
///
|
||||
/// // Now we can practically handle it just as we would handle the Public itself:
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct PublicBox<const N: usize> {
|
||||
/// The inner [Box] around the [Public].
|
||||
pub inner: Box<Public<N>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PublicBox<N> {
|
||||
/// Create a new [PublicBox] from a byte slice
|
||||
/// Create a new [PublicBox] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::from_slice(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [PublicBox] from a byte array
|
||||
/// Create a new [PublicBox] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a zero initialized [PublicBox]
|
||||
/// Create a zero initialized [PublicBox].
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a random initialized [PublicBox]
|
||||
/// Create a random initialized [PublicBox].
|
||||
pub fn random() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::random()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [PublicBox]
|
||||
/// Randomize all bytes in an existing [PublicBox].
|
||||
pub fn randomize(&mut self) {
|
||||
self.inner.randomize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.inner.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&**self, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
self.inner.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
self.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut p = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
|
||||
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory.
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// A vector is used here to ensure heap allocation without copy from stack
|
||||
// A vector is used here to ensure heap allocation without copy from stack.
|
||||
let mut f = vec![0u8; F];
|
||||
let mut v = PublicBox::zero();
|
||||
let p = path.as_ref();
|
||||
@@ -295,16 +357,20 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store_b64::<F, P>(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
writer: W,
|
||||
@@ -320,6 +386,7 @@ mod tests {
|
||||
#[allow(clippy::module_inception)]
|
||||
mod tests {
|
||||
use crate::{Public, PublicBox};
|
||||
use rand::Fill;
|
||||
use rosenpass_util::{
|
||||
b64::b64_encode,
|
||||
file::{
|
||||
@@ -449,5 +516,112 @@ mod tests {
|
||||
fn test_public_box_load_store_base64() {
|
||||
run_base64_load_store_test::<PublicBox<N>>();
|
||||
}
|
||||
|
||||
/// Test the debug print function for [Public]
|
||||
#[test]
|
||||
fn test_debug_public() {
|
||||
let p: Public<32> = Public::zero();
|
||||
let _ = format!("{:?}", p);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_sized() {
|
||||
let p: Public<32> = Public::zero();
|
||||
let borrowed: &[u8; 32] = &p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_sized_mut() {
|
||||
let mut p: Public<32> = Public::zero();
|
||||
let borrowed: &mut [u8; 32] = &mut p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_unsized() {
|
||||
use std::borrow::Borrow;
|
||||
let p: Public<32> = Public::zero();
|
||||
let borrowed: &[u8] = p.borrow();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_unsized_mut() {
|
||||
use std::borrow::BorrowMut;
|
||||
let mut p: Public<32> = Public::zero();
|
||||
let borrowed: &mut [u8] = p.borrow_mut();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly created from a slice.
|
||||
#[test]
|
||||
fn test_public_box_from_slice() {
|
||||
let my_slice: &[u8; 32] = &[0; 32];
|
||||
let p: PublicBox<32> = PublicBox::from_slice(my_slice);
|
||||
assert_eq!(p.deref(), my_slice);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] can correctly be created with its [PublicBox::new] function.
|
||||
#[test]
|
||||
fn test_public_box_new() {
|
||||
let pb = PublicBox::new([42; 32]);
|
||||
assert_eq!(pb.deref(), &[42; 32]);
|
||||
}
|
||||
|
||||
/// Test the randomize functionality of [PublicBox].
|
||||
#[test]
|
||||
fn test_public_box_randomize() {
|
||||
let mut pb: PublicBox<32> = PublicBox::zero();
|
||||
pb.randomize();
|
||||
pb.try_fill(&mut crate::rand::rng()).unwrap();
|
||||
// Can't really assert anything here until we have can predict the randomness
|
||||
// by derandomizing the RNG for tests.
|
||||
}
|
||||
|
||||
/// Test the [Debug] print of [PublicBox]
|
||||
#[test]
|
||||
fn test_public_box_debug() {
|
||||
let pb: PublicBox<32> = PublicBox::new([42; 32]);
|
||||
let _ = format!("{:?}", pb);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_box_sized() {
|
||||
let p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &[u8; 32] = &p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a mutable u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_box_sized_mut() {
|
||||
let mut p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &mut [u8; 32] = &mut p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_box_unsized() {
|
||||
use std::borrow::Borrow;
|
||||
let p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &[u8] = p.borrow();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_box_unsized_mut() {
|
||||
use std::borrow::BorrowMut;
|
||||
let mut p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &mut [u8] = p.borrow_mut();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! This module provides functionality for generating random numbers using the [rand] crate.
|
||||
|
||||
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
|
||||
/// Get the default [Rng].
|
||||
pub fn rng() -> Rng {
|
||||
rand::thread_rng()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ thread_local! {
|
||||
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
|
||||
}
|
||||
|
||||
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
|
||||
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
|
||||
where
|
||||
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
|
||||
@@ -47,37 +48,47 @@ where
|
||||
.unwrap_or_else(|| f(None))
|
||||
}
|
||||
|
||||
// Wrapper around SecretBox that applies automatic zeroization
|
||||
/// Wrapper around SecretBox that applies automatic zeroization.
|
||||
#[derive(Debug)]
|
||||
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
|
||||
|
||||
impl<T: Zeroize> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
|
||||
/// [Zeroize].
|
||||
fn new(boxed: T) -> Self {
|
||||
ZeroizingSecretBox(Some(secret_box(boxed)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] for the type `T`,
|
||||
/// which must implement [Zeroize] but does not have to be [Sized].
|
||||
fn from_secret_box(inner: SecretBox<T>) -> Self {
|
||||
Self(Some(inner))
|
||||
}
|
||||
|
||||
/// Consumes the [ZeroizingSecretBox] and returns the content in a [SecretBox] for the type `T`,
|
||||
/// which must implement [Zeroize] but does not have to be [Sized].
|
||||
fn take(mut self) -> SecretBox<T> {
|
||||
self.0.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that a Secret is always zeroized when it is dropped.
|
||||
impl<T: Zeroize + ?Sized> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
|
||||
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
|
||||
fn zeroize(&mut self) {
|
||||
if let Some(inner) = &mut self.0 {
|
||||
let inner: &mut SecretBox<T> = inner; // type annotation
|
||||
let inner: &mut SecretBox<T> = inner; // Type annotation.
|
||||
inner.zeroize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
||||
/// Releases the memory of this [ZeroizingSecretBox]. In contrast to usual implementations
|
||||
/// of [Drop], we zeroize the memory before dropping it. This fulfills the promises we make
|
||||
/// by implementing [ZeroizeOnDrop].
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
@@ -86,29 +97,32 @@ impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
||||
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
|
||||
type Target = T;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &T {
|
||||
self.0.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.0.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool that stores secret memory allocations
|
||||
/// Pool that stores secret memory allocations.
|
||||
///
|
||||
/// Allocation of secret memory is expensive. Thus, this struct provides a
|
||||
/// pool of secret memory, readily available to yield protected, slices of
|
||||
/// memory.
|
||||
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
||||
struct SecretMemoryPool {
|
||||
/// A pool to reuse secret memory
|
||||
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
|
||||
}
|
||||
|
||||
impl SecretMemoryPool {
|
||||
/// Create a new [SecretMemoryPool]
|
||||
/// Create a new [SecretMemoryPool].
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -116,7 +130,7 @@ impl SecretMemoryPool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return secret back to the pool for future re-use
|
||||
/// Return secret back to the pool for future re-use.
|
||||
pub fn release<const N: usize>(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) {
|
||||
sec.zeroize();
|
||||
|
||||
@@ -134,7 +148,7 @@ impl SecretMemoryPool {
|
||||
/// Take protected memory from the pool, allocating new one if no suitable
|
||||
/// chunk is found in the inventory.
|
||||
///
|
||||
/// The secret is guaranteed to be full of nullbytes
|
||||
/// The secret is guaranteed to be full of nullbytes.
|
||||
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
|
||||
let entry = self.pool.entry(N).or_default();
|
||||
let inner = match entry.pop() {
|
||||
@@ -145,22 +159,50 @@ impl SecretMemoryPool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage for secret data
|
||||
/// [Secret] stores its memory in a way that makes it difficult for threat actors to access it
|
||||
/// without permission. This is achieved through the following mechanisms:
|
||||
/// 1. Data in a [Secret] is stored in memory that is allocated and managed by memory allocators
|
||||
/// that make it more difficult for threat actors to access the memory. Specifically, the
|
||||
/// allocators from [memsec] are supported.
|
||||
/// 2. Memory that is allocated for a [Secret] is zeroized before it is used for anything else.
|
||||
///
|
||||
/// In order to use a [Secret], we have to decide on the secure allocator to use beforehand
|
||||
/// calling either
|
||||
/// [secret_policy_use_only_malloc_secrets](crate::secret_policy_use_only_malloc_secrets),
|
||||
/// [secret_policy_use_only_memfd_secrets](crate::secret_policy_use_only_memfd_secrets)
|
||||
/// or [secret_policy_try_use_memfd_secrets](crate::secret_policy_try_use_memfd_secrets).
|
||||
///
|
||||
/// You can use a [Secret] as follows:
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
/// let mut my_secret: Secret<32> = Secret::zero();
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_secret.randomize();
|
||||
/// // In case I accidentally print my secret in a debug, it's still not leaked:
|
||||
/// assert_eq!(format!("{:?}", my_secret), "<SECRET>");
|
||||
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
|
||||
/// my_secret.zeroize();
|
||||
/// ```
|
||||
pub struct Secret<const N: usize> {
|
||||
storage: Option<ZeroizingSecretBox<[u8; N]>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> Secret<N> {
|
||||
/// Create a new [Secret] from a byte-slice.
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
let mut new_self = Self::zero();
|
||||
new_self.secret_mut().copy_from_slice(slice);
|
||||
new_self
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is zero initialized
|
||||
/// Returns a new [Secret] that is zero initialized.
|
||||
pub fn zero() -> Self {
|
||||
// Using [SecretMemoryPool] here because this operation is expensive,
|
||||
// yet it is used in hot loops
|
||||
// yet it is used in hot loops.
|
||||
let buf = with_secret_memory_pool(|pool| {
|
||||
pool.map(|p| p.take())
|
||||
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
|
||||
@@ -169,28 +211,30 @@ impl<const N: usize> Secret<N> {
|
||||
Self { storage: Some(buf) }
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is randomized
|
||||
/// Returns a new [Secret] that is randomized.
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Sets all data an existing secret to random bytes
|
||||
/// Sets all data of an existing [Secret] to random bytes.
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data
|
||||
/// Borrows the data.
|
||||
pub fn secret(&self) -> &[u8; N] {
|
||||
self.storage.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data mutably
|
||||
/// Borrows the data mutably.
|
||||
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
||||
self.storage.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Secret<N> {
|
||||
/// Tries to fill this [Secret] with random data. The [Secret] is first zeroized
|
||||
/// to make sure that the barriers from the zeroize crate take effect.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
// Zeroize self first just to make sure the barriers from the zeroize create take
|
||||
// effect to prevent the compiler from optimizing this away.
|
||||
@@ -200,8 +244,10 @@ impl<const N: usize> Randomize for Secret<N> {
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that a [Secret] is always zeroized when it is dropped.
|
||||
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
|
||||
impl<const N: usize> Zeroize for Secret<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn zeroize(&mut self) {
|
||||
if let Some(inner) = &mut self.storage {
|
||||
inner.zeroize()
|
||||
@@ -209,7 +255,14 @@ impl<const N: usize> Zeroize for Secret<N> {
|
||||
}
|
||||
}
|
||||
|
||||
// Our implementation of Drop gives back the allocated secret memory to the secret memory pool.
|
||||
impl<const N: usize> Drop for Secret<N> {
|
||||
/// Release the memory of this [Secret]. In contrast to usual implementations to [Drop] we
|
||||
/// do the following:
|
||||
/// 1. The memory of this [Secret] gets zeroized as required by [ZeroizeOnDrop].
|
||||
/// 2. The memory is returned to a memory pool of specially secure memory to be reused.
|
||||
///
|
||||
/// This behaviour fulfills the promises we make by implementing [ZeroizeOnDrop].
|
||||
fn drop(&mut self) {
|
||||
with_secret_memory_pool(|pool| {
|
||||
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
|
||||
@@ -240,6 +293,7 @@ impl<const N: usize> fmt::Debug for Secret<N> {
|
||||
impl<const N: usize> LoadValue for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
@@ -253,6 +307,7 @@ impl<const N: usize> LoadValue for Secret<N> {
|
||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut f: Secret<F> = Secret::random();
|
||||
let mut v = Self::random();
|
||||
@@ -272,6 +327,7 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let p = path.as_ref();
|
||||
|
||||
@@ -291,6 +347,7 @@ impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||
impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
|
||||
let mut f: Secret<F> = Secret::random();
|
||||
let encoded_str = b64_encode(self.secret(), f.secret_mut())
|
||||
@@ -307,11 +364,13 @@ impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||
impl<const N: usize> StoreSecret for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
|
||||
Ok(())
|
||||
@@ -320,7 +379,10 @@ impl<const N: usize> StoreSecret for Secret<N> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test_spawn_process_provided_policies;
|
||||
use crate::{
|
||||
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
|
||||
test_spawn_process_provided_policies,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use std::{fs, os::unix::fs::PermissionsExt};
|
||||
@@ -328,7 +390,7 @@ mod test {
|
||||
|
||||
procspawn::enable_test_support!();
|
||||
|
||||
/// check that we can alloc using the magic pool
|
||||
/// Check that we can alloc using the magic pool.
|
||||
#[test]
|
||||
fn secret_memory_pool_take() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -339,7 +401,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// check that a secret lives, even if its [SecretMemoryPool] is deleted
|
||||
/// Check that a secret lives, even if its [SecretMemoryPool] is deleted.
|
||||
#[test]
|
||||
fn secret_memory_pool_drop() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -351,7 +413,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// check that a secret can be reborn, freshly initialized with zero
|
||||
/// Check that a secret can be reborn, freshly initialized with zero.
|
||||
#[test]
|
||||
fn secret_memory_pool_release() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -372,7 +434,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// test loading a secret from an example file, and then storing it again in a different file
|
||||
/// Test loading a secret from an example file, and then storing it again in a different file.
|
||||
#[test]
|
||||
fn test_secret_load_store() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -409,7 +471,8 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// test loading a base64 encoded secret from an example file, and then storing it again in a different file
|
||||
/// Test loading a base64 encoded secret from an example file, and then storing it again in
|
||||
/// a different file.
|
||||
#[test]
|
||||
fn test_secret_load_store_base64() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -463,4 +526,33 @@ mod test {
|
||||
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test the creation of a [ZeroizingSecretBox] using its [new](ZeroizingSecretBox::new)
|
||||
/// function.
|
||||
#[test]
|
||||
fn test_zeroizing_secret_box_new() {
|
||||
struct DummyZeroizing {
|
||||
inner_dummy: [u8; 32],
|
||||
}
|
||||
impl Zeroize for DummyZeroizing {
|
||||
fn zeroize(&mut self) {
|
||||
self.inner_dummy = [0; 32];
|
||||
}
|
||||
}
|
||||
let dummy = DummyZeroizing {
|
||||
inner_dummy: [42; 32],
|
||||
};
|
||||
secret_policy_use_only_malloc_secrets();
|
||||
let mut zsb: ZeroizingSecretBox<DummyZeroizing> = ZeroizingSecretBox::new(dummy);
|
||||
zsb.zeroize();
|
||||
assert_eq!(zsb.inner_dummy, [0; 32]);
|
||||
}
|
||||
|
||||
/// Test the debug print of [Secret].
|
||||
#[test]
|
||||
fn test_debug_secret() {
|
||||
secret_policy_use_only_malloc_secrets();
|
||||
let my_secret: Secret<32> = Secret::zero();
|
||||
assert_eq!(format!("{:?}", my_secret), "<SECRET>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
|
||||
use std::ops::BitXorAssign;
|
||||
|
||||
// Destination functions return some value that implements the To trait.
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would
|
||||
// be without destination parameters
|
||||
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: BitXorAssign + Clone,
|
||||
{
|
||||
// Custom implementations of the to trait can be created, but the easiest
|
||||
// way to create them is to use the provided helper functions like with_destination.
|
||||
with_destination(move |dst: &mut [T]| {
|
||||
assert!(src.len() == dst.len());
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Functions with destination copying data between slices and arrays.
|
||||
//! Functions that make it easy to copy data between arrays and slices using functions with
|
||||
//! destinations. See the specific functions for examples and more explanations.
|
||||
|
||||
use crate::{with_destination, To};
|
||||
|
||||
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the two slices have different lengths.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice;
|
||||
/// let to_function = copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -23,6 +35,19 @@ where
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if destination is shorter than origin.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least_src;
|
||||
/// let to_function = copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -34,6 +59,18 @@ where
|
||||
/// destination.
|
||||
///
|
||||
/// Copies as much data as is present in the shorter slice.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least;
|
||||
/// let to_function = copy_slice_least(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -47,6 +84,24 @@ where
|
||||
/// Function with destination that attempts to copy data from origin into the destination.
|
||||
///
|
||||
/// Will return None if the slices are of different lengths.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice;
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the slices do not have the same length.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -62,6 +117,26 @@ where
|
||||
/// Destination may be longer than origin.
|
||||
///
|
||||
/// Will return None if the destination is shorter than origin.
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice_least_src;
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 15];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the destination is to short.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation, the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -72,6 +147,18 @@ where
|
||||
}
|
||||
|
||||
/// Function with destination that copies all data between two array references.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::ops::copy_array;
|
||||
/// use rosenpass_to::To;
|
||||
/// let my_arr: [u8; 32] = [0; 32];
|
||||
/// let to_function = copy_array(&my_arr);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
//! This module provides the [Beside] struct. In the context of functions with targets,
|
||||
//! [Beside] structures the destination value and the return value unmistakably and offers useful
|
||||
//! helper functions to work with them.
|
||||
|
||||
use crate::CondenseBeside;
|
||||
|
||||
/// Named tuple holding the return value and the output from a function with destinations.
|
||||
/// Named tuple holding the return value and the destination from a function with destinations.
|
||||
/// See the respective functions for usage examples.
|
||||
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||
|
||||
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
/// Perform beside condensation. See [CondenseBeside]
|
||||
/// Perform beside condensation. See [CondenseBeside] for more details.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
|
||||
(val, ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Beside;
|
||||
|
||||
#[test]
|
||||
fn from_tuple() {
|
||||
let tuple = (21u8, 42u16);
|
||||
let beside: Beside<u8, u16> = Beside::from(tuple);
|
||||
assert_eq!(beside.dest(), &21u8);
|
||||
assert_eq!(beside.ret(), &42u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_beside() {
|
||||
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
|
||||
type U8u16 = (u8, u16);
|
||||
let tuple = U8u16::from(beside);
|
||||
assert_eq!(tuple.0, 21u8);
|
||||
assert_eq!(tuple.1, 42u16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/// Beside condensation.
|
||||
//! This module provides condensation for values that stand side by side,
|
||||
//! which is often useful when working with destination parameters. See [CondenseBeside]
|
||||
//! for more details.
|
||||
|
||||
/// Condenses two values that stand beside each other into one value.
|
||||
/// For example, a blanked implementation for [Result<(), Error>](Result) is provided. If
|
||||
/// `condense(val)` is called on such an object, a [Result<Val, Error>](Result) will
|
||||
/// be returned, if `val` is of type `Val`.
|
||||
///
|
||||
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
|
||||
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
|
||||
@@ -6,6 +13,19 @@
|
||||
///
|
||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||
/// condense trait.
|
||||
///
|
||||
/// # Example
|
||||
/// As an example implementation, we take a look at the blanket implementation for [Option]
|
||||
/// ```ignore
|
||||
/// impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
/// type Condensed = Option<Val>;
|
||||
///
|
||||
/// /// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
/// fn condense(self, ret: Val) -> Option<Val> {
|
||||
/// self.map(|()| ret)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait CondenseBeside<Val> {
|
||||
/// The type that results from condensation.
|
||||
type Condensed;
|
||||
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
|
||||
impl<Val> CondenseBeside<Val> for () {
|
||||
type Condensed = Val;
|
||||
|
||||
/// Replaces this empty tuple with `ret`.
|
||||
fn condense(self, ret: Val) -> Val {
|
||||
ret
|
||||
}
|
||||
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
|
||||
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
type Condensed = Result<Val, Error>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Result] with `ret`.
|
||||
fn condense(self, ret: Val) -> Result<Val, Error> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
type Condensed = Option<Val>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
fn condense(self, ret: Val) -> Option<Val> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
/// Helper performing explicit unsized coercion.
|
||||
/// Used by the [to](crate::to()) function.
|
||||
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
|
||||
//! types. See [DstCoercion] for more details.
|
||||
|
||||
/// Helper Trait for performing explicit coercion from [Sized] types to
|
||||
/// [?Sized][core::marker::Sized] types. It's used by the [to](crate::to()) function.
|
||||
///
|
||||
/// We provide blanket implementations for any [Sized] type and for any array of [Sized] types.
|
||||
///
|
||||
/// # Example
|
||||
/// It can be used as follows:
|
||||
/// ```
|
||||
/// # use rosenpass_to::DstCoercion;
|
||||
/// // Consider a sized type like this example:
|
||||
/// struct SizedStruct {
|
||||
/// x: u32
|
||||
/// }
|
||||
/// // Then we can coerce it to be unsized:
|
||||
/// let mut sized = SizedStruct { x: 42 };
|
||||
/// assert_eq!(42, sized.coerce_dest().x);
|
||||
///
|
||||
/// // Analogously, we can coerce arrays to slices:
|
||||
/// let mut sized_array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
/// let un_sized: &[i32] = sized_array.coerce_dest();
|
||||
/// assert_eq!(un_sized, un_sized);
|
||||
/// ```
|
||||
pub trait DstCoercion<Dst: ?Sized> {
|
||||
/// Performs an explicit coercion to the destination type.
|
||||
fn coerce_dest(&mut self) -> &mut Dst;
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
||||
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
||||
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or
|
||||
//! some sized variant of
|
||||
//! `Dst` (e.g. `[u8; 64]`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The
|
||||
//! ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as
|
||||
//! `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>)
|
||||
//! – The combiation of Val and Ret after condensing was applied
|
||||
//! (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
|
||||
pub mod beside;
|
||||
pub mod condense;
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
//! This module provides the [To::to] function which allows to use functions with destination in
|
||||
//! a manner akin to that of a variable assignment. See [To::to] for more details.
|
||||
|
||||
use crate::{DstCoercion, To};
|
||||
|
||||
/// Alias for [To::to] moving the destination to the left.
|
||||
///
|
||||
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
|
||||
/// the variable to assign to on the left and the generating function on the right.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// // Using the to function to have data flowing from the right to the left,
|
||||
/// // performing something akin to a variable assignment.
|
||||
/// use rosenpass_to::ops::copy_slice_least;
|
||||
/// # use rosenpass_to::to;
|
||||
/// let mut dst = b" ".to_vec();
|
||||
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
|
||||
/// assert_eq!(&dst[..], b"Hello World");
|
||||
/// ```
|
||||
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
|
||||
where
|
||||
Coercable: ?Sized + DstCoercion<Dst>,
|
||||
|
||||
@@ -1,12 +1,46 @@
|
||||
//! Module that contains the [To] crate which is the container used to
|
||||
//! implement the core functionality of this crate.
|
||||
|
||||
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<Destination,
|
||||
/// Return_value`.
|
||||
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
|
||||
/// Return_value>`.
|
||||
///
|
||||
/// 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| ...)](crate::with_destination) higher order function.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a very simple example for how the Trait can be implemented. More examples for
|
||||
/// how this Trait is best implemented can be found in the overall [crate documentation](crate).
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
///
|
||||
/// // This is a simple wrapper around a String that can be written into a byte array using to.
|
||||
/// struct StringToBytes {
|
||||
/// inner: String
|
||||
/// }
|
||||
///
|
||||
/// impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// let bytes = self.inner.as_bytes();
|
||||
/// if bytes.len() > out.len() {
|
||||
/// return Err("out is to short".to_string());
|
||||
/// }
|
||||
/// for i in 0..bytes.len() {
|
||||
/// (*out)[i] = bytes[i];
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let mut buffer: [u8; 10] = [0; 10];
|
||||
/// let result = string_to_bytes.to(&mut buffer);
|
||||
/// assert_eq!(buffer, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
||||
///
|
||||
@@ -19,6 +53,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [self.to_this_beside]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let Beside(dst, result) = string_to_bytes.to_this_beside(|| [0; 10]);
|
||||
/// assert_eq!(dst, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: BorrowMut<Dst>,
|
||||
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
|
||||
/// Generate a destination on the fly using default.
|
||||
///
|
||||
/// Uses [Default] to create a value,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value_beside](To::to_value_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let Beside(dst, ret) = copy_array(&[42u8; 16]).to_value_beside();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value_beside(self) -> Beside<Dst, Ret>
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// when the Destination is unsized.
|
||||
///
|
||||
/// This could be the case when the destination is an `[u8]` for instance.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect_beside](To::collect_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let Beside(dst, ret) = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect_beside<Val>(self) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [Self::to_this]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let result = string_to_bytes.to_this_beside(|| [0; 10]).condense();
|
||||
/// assert!(result.is_ok());
|
||||
/// assert_eq!(result.unwrap(), [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
///
|
||||
/// ```
|
||||
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Ret: CondenseBeside<Val>,
|
||||
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value](To::to_value).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let dst = copy_array(&[42u8; 16]).to_value_beside().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect](To::collect).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
//! The module provides the [with_destination] function, which makes it easy to create
|
||||
//! a [To] from a lambda function. See [with_destination] and the [crate documentation](crate)
|
||||
//! for more details and examples.
|
||||
|
||||
use crate::To;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A struct that wraps a closure and implements the `To` trait
|
||||
/// 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`.
|
||||
/// and return `Ret`. It is only internally used to implement [with_destination].
|
||||
///
|
||||
/// # 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`
|
||||
/// * `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>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
@@ -17,11 +21,11 @@ where
|
||||
{
|
||||
/// The function to call.
|
||||
fun: Fun,
|
||||
/// Phantom data to hold the destination type
|
||||
/// Phantom data to hold the destination type.
|
||||
_val: PhantomData<Box<Dst>>,
|
||||
}
|
||||
|
||||
/// Implementation of the `To` trait for ToClosure
|
||||
/// 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>
|
||||
@@ -33,6 +37,7 @@ where
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `out` - Mutable reference to the destination
|
||||
/// See the tutorial in [readme.md] for examples and more explanations.
|
||||
fn to(self, out: &mut Dst) -> Ret {
|
||||
(self.fun)(out)
|
||||
}
|
||||
@@ -48,7 +53,24 @@ where
|
||||
/// * `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 the [crate documentation](crate) for more examples and more explanations.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_to::with_destination;
|
||||
/// use crate::rosenpass_to::To;
|
||||
/// let my_origin_data: [u8; 16]= [2; 16];
|
||||
/// let times_two = with_destination( move |dst: &mut [u8; 16]| {
|
||||
/// for (dst, org) in dst.iter_mut().zip(my_origin_data.iter()) {
|
||||
/// *dst = dst.clone() * org;
|
||||
/// }
|
||||
/// });
|
||||
/// let mut dst: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||||
/// times_two.to(&mut dst);
|
||||
/// for i in 0..16 {
|
||||
/// assert_eq!(dst[i], (2 * i) as u8);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
|
||||
@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
|
||||
|
||||
/// A type that can be incrementally built from a type that can [Build] it
|
||||
///
|
||||
/// This is similar to an option, where [Self::Void] is [std::Option::None],
|
||||
/// [Self::Product] is [std::Option::Some], except that there is a third
|
||||
/// This is similar to an option, where [Self::Void] is [std::option::Option::None],
|
||||
/// [Self::Product] is [std::option::Option::Some], except that there is a third
|
||||
/// intermediate state [Self::Builder] that represents a Some/Product value
|
||||
/// in the process of being made.
|
||||
///
|
||||
@@ -508,9 +508,9 @@ where
|
||||
matches!(self, Self::Void)
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`InProgress`].
|
||||
/// Returns `true` if the construction site is in the [`Builder`] phase.
|
||||
///
|
||||
/// [`InProgress`]: ConstructionSite::InProgress
|
||||
/// [`Builder`]: ConstructionSite::Builder
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -541,9 +541,10 @@ where
|
||||
matches!(self, Self::Builder(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`Done`].
|
||||
/// Returns `true` if the construction site is in the [`Product`] phase and
|
||||
/// is therefore done.
|
||||
///
|
||||
/// [`Done`]: ConstructionSite::Done
|
||||
/// [`Product`]: ConstructionSite::Product
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// A collection of control flow utility macros
|
||||
//! A collection of control flow utility macros
|
||||
|
||||
#[macro_export]
|
||||
/// A simple for loop to repeat a $body a number of times
|
||||
@@ -33,7 +33,7 @@ macro_rules! repeat {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 0);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_unless!(false, 1);
|
||||
/// 0
|
||||
@@ -65,7 +65,7 @@ macro_rules! return_unless {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 1);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_if!(false, 1);
|
||||
/// 0
|
||||
@@ -98,7 +98,7 @@ macro_rules! return_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 5);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for _ in 0..10 {
|
||||
/// for j in 0..20 {
|
||||
@@ -134,7 +134,7 @@ macro_rules! break_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 9);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for i in 0..10 {
|
||||
/// continue_if!(i == 5, 'one);
|
||||
|
||||
141
util/src/fd.rs
141
util/src/fd.rs
@@ -89,6 +89,30 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
||||
///
|
||||
/// Will panic if the given file descriptor is negative of or larger than
|
||||
/// the file descriptor numbers permitted by the operating system.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::io::Read;
|
||||
/// # use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
/// # use std::os::fd::IntoRawFd;
|
||||
/// # use rustix::fd::AsFd;
|
||||
/// # use rosenpass_util::fd::mask_fd;
|
||||
///
|
||||
/// // Open a temporary file
|
||||
/// let fd = tempfile::tempfile().unwrap().into_raw_fd();
|
||||
/// assert!(fd >= 0);
|
||||
///
|
||||
/// // Mask the file descriptor
|
||||
/// mask_fd(fd).unwrap();
|
||||
///
|
||||
/// // Verify the file descriptor now points to `/dev/null`
|
||||
/// // Reading from `/dev/null` always returns 0 bytes
|
||||
/// let mut replaced_file = unsafe { File::from_raw_fd(fd) };
|
||||
/// let mut buffer = [0u8; 4];
|
||||
/// let bytes_read = replaced_file.read(&mut buffer).unwrap();
|
||||
/// assert_eq!(bytes_read, 0);
|
||||
/// ```
|
||||
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
||||
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting,
|
||||
// it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd
|
||||
@@ -286,14 +310,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinguish different socket address familys; e.g. IP and unix sockets
|
||||
/// Distinguish different socket address families; e.g. IP and unix sockets
|
||||
#[cfg(target_os = "linux")]
|
||||
pub trait GetSocketDomain {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Retrieve the socket domain (address family)
|
||||
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
|
||||
/// Alias for [socket_domain]
|
||||
/// Alias for [Self::socket_domain]
|
||||
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
|
||||
self.socket_domain()
|
||||
}
|
||||
@@ -320,9 +344,67 @@ where
|
||||
pub trait GetUnixSocketType {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Check if the socket is a unix stream socket
|
||||
|
||||
/// Checks whether the socket is a Unix stream socket.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(true)` if the socket is a Unix stream socket.
|
||||
/// - `Ok(false)` if the socket is not a Unix stream socket.
|
||||
/// - `Err(Self::Error)` if there is an error while performing the check.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::UnixListener;
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().is_unix_stream_socket(), Ok(true)));
|
||||
/// ```
|
||||
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
|
||||
/// Returns Ok(()) only if the underlying socket is a unix stream socket
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::{UnixDatagram, UnixListener};
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().demand_unix_stream_socket(), Ok(())));
|
||||
/// // Error if the FD is a file
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// assert_eq!(temp_file.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Socket operation on non-socket (os error 88)"
|
||||
/// );
|
||||
/// // Error if the FD is a Unix stream with a wrong mode (e.g. Datagram)
|
||||
/// let f = {
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixDatagram::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert_eq!(f.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Expected unix socket in stream mode, but mode is SocketType(2)"
|
||||
/// );
|
||||
/// ```
|
||||
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
@@ -352,16 +434,65 @@ where
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Distinguish between different network socket protocols (e.g. tcp, udp)
|
||||
pub trait GetSocketProtocol {
|
||||
/// Retrieve the socket protocol
|
||||
/// Retrieves the socket's protocol.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Some(Protocol))`: The protocol of the socket if available.
|
||||
/// - `Ok(None)`: If the protocol information is unavailable.
|
||||
/// - `Err(rustix::io::Errno)`: If an error occurs while retrieving the protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().socket_protocol().unwrap().unwrap(), rustix::net::ipproto::UDP);
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
|
||||
/// Check if the socket is a udp socket
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(socket.as_fd().is_udp_socket().unwrap());
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert!(!socket.as_fd().is_udp_socket().unwrap());
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
|
||||
self.socket_protocol()?
|
||||
.map(|p| p == rustix::net::ipproto::UDP)
|
||||
.unwrap_or(false)
|
||||
.ok()
|
||||
}
|
||||
/// Return Ok(()) only if the socket is a udp socket
|
||||
|
||||
/// Ensures that the socket is a UDP socket, returning an error otherwise.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(())` if the socket is a UDP socket.
|
||||
/// - `Err(anyhow::Error)` if the socket is not a UDP socket or if an error occurs retrieving the socket protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(matches!(socket.as_fd().demand_udp_socket(), Ok(())));
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().demand_udp_socket().unwrap_err().to_string(),
|
||||
/// "Not a udp socket, instead socket protocol is: Protocol(6)");
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn demand_udp_socket(&self) -> anyhow::Result<()> {
|
||||
match self.socket_protocol() {
|
||||
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),
|
||||
|
||||
@@ -244,7 +244,6 @@
|
||||
use std::{borrow::Borrow, io};
|
||||
|
||||
use anyhow::ensure;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
/// Generic trait for accessing [std::io::Error::kind]
|
||||
///
|
||||
|
||||
@@ -22,7 +22,7 @@ pub mod io;
|
||||
pub mod length_prefix_encoding;
|
||||
/// Memory manipulation and allocation utilities.
|
||||
pub mod mem;
|
||||
/// MIO integration utilities.
|
||||
/// [MIO (Metal I/O)](https://docs.rs/crate/mio/) integration utilities.
|
||||
pub mod mio;
|
||||
/// Extended Option type functionality.
|
||||
pub mod option;
|
||||
|
||||
166
util/src/mem.rs
166
util/src/mem.rs
@@ -1,10 +1,33 @@
|
||||
//!
|
||||
//! This module provides functions for copying data, concatenating byte arrays,
|
||||
//! and various traits and types that help manage values, including preventing
|
||||
//! drops, discarding results, and swapping values.
|
||||
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::cmp::min;
|
||||
use std::mem::{forget, swap};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Concatenate two byte arrays
|
||||
// TODO: Zeroize result?
|
||||
/// Concatenate multiple byte slices into a fixed-size byte array.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the concatenated length does not match the declared length.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::cat;
|
||||
/// let arr = cat!(6; b"abc", b"def");
|
||||
/// assert_eq!(&arr, b"abcdef");
|
||||
///
|
||||
/// let err = std::panic::catch_unwind(|| cat!(5; b"abc", b"def"));
|
||||
/// assert!(matches!(err, Err(_)));
|
||||
///
|
||||
/// let err = std::panic::catch_unwind(|| cat!(7; b"abc", b"def"));
|
||||
/// assert!(matches!(err, Err(_)));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! cat {
|
||||
($len:expr; $($toks:expr),+) => {{
|
||||
@@ -22,12 +45,37 @@ macro_rules! cat {
|
||||
}
|
||||
|
||||
// TODO: consistent inout ordering
|
||||
/// Copy all bytes from `src` to `dst`. The lengths must match.
|
||||
/// Copy bytes from `src` to `dst`, requiring equal lengths.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if lengths differ.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::cpy;
|
||||
/// let src = [1, 2, 3];
|
||||
/// let mut dst = [0; 3];
|
||||
/// cpy(&src, &mut dst);
|
||||
/// assert_eq!(dst, [1, 2, 3]);
|
||||
/// ```
|
||||
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
dst.borrow_mut().copy_from_slice(src.borrow());
|
||||
}
|
||||
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length,
|
||||
/// copy as many bytes as possible.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::cpy_min;
|
||||
/// let src = [1, 2, 3, 4];
|
||||
/// let mut dst = [0; 2];
|
||||
/// cpy_min(&src, &mut dst);
|
||||
/// assert_eq!(dst, [1, 2]);
|
||||
/// ```
|
||||
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
let src = src.borrow();
|
||||
let dst = dst.borrow_mut();
|
||||
@@ -35,20 +83,30 @@ pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d
|
||||
dst[..len].copy_from_slice(&src[..len]);
|
||||
}
|
||||
|
||||
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed
|
||||
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying
|
||||
/// variable is freed
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::Forgetting;
|
||||
/// let f = Forgetting::new(String::from("hello"));
|
||||
/// assert_eq!(&*f, "hello");
|
||||
/// let val = f.extract();
|
||||
/// assert_eq!(val, "hello");
|
||||
/// ```
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
|
||||
pub struct Forgetting<T> {
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Forgetting<T> {
|
||||
/// Creates a new `Forgetting<T>` instance containing the given value.
|
||||
/// Create a new `Forgetting` wrapping `value`.
|
||||
pub fn new(value: T) -> Self {
|
||||
let value = Some(value);
|
||||
Self { value }
|
||||
Self { value: Some(value) }
|
||||
}
|
||||
|
||||
/// Extracts and returns the contained value, consuming self.
|
||||
/// Consume and return the inner value.
|
||||
pub fn extract(mut self) -> T {
|
||||
let mut value = None;
|
||||
swap(&mut value, &mut self.value);
|
||||
@@ -97,6 +155,13 @@ impl<T> Drop for Forgetting<T> {
|
||||
}
|
||||
|
||||
/// A trait that provides a method to discard a value without explicitly handling its results.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::DiscardResultExt;
|
||||
/// let result: () = (|| { return 42 })().discard_result(); // Just discard
|
||||
/// ```
|
||||
pub trait DiscardResultExt {
|
||||
/// Consumes and discards a value without doing anything with it.
|
||||
fn discard_result(self);
|
||||
@@ -107,8 +172,16 @@ impl<T> DiscardResultExt for T {
|
||||
}
|
||||
|
||||
/// Trait that provides a method to explicitly forget values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::ForgetExt;
|
||||
/// let s = String::from("no drop");
|
||||
/// s.forget(); // destructor not run
|
||||
/// ```
|
||||
pub trait ForgetExt {
|
||||
/// Consumes and forgets a value, preventing its destructor from running.
|
||||
/// Forget the value.
|
||||
fn forget(self);
|
||||
}
|
||||
|
||||
@@ -119,10 +192,23 @@ impl<T> ForgetExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides methods for swapping values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::SwapWithExt;
|
||||
/// let mut x = 10;
|
||||
/// let mut y = x.swap_with(20);
|
||||
/// assert_eq!(x, 20);
|
||||
/// assert_eq!(y, 10);
|
||||
/// y.swap_with_mut(&mut x);
|
||||
/// assert_eq!(x, 10);
|
||||
/// assert_eq!(y, 20);
|
||||
/// ```
|
||||
pub trait SwapWithExt {
|
||||
/// Takes ownership of `other` and swaps its value with `self`, returning the original value.
|
||||
/// Swap values and return the old value of `self`.
|
||||
fn swap_with(&mut self, other: Self) -> Self;
|
||||
/// Swaps the values between `self` and `other` in place.
|
||||
/// Swap values in place with another mutable reference.
|
||||
fn swap_with_mut(&mut self, other: &mut Self);
|
||||
}
|
||||
|
||||
@@ -138,8 +224,18 @@ impl<T> SwapWithExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides methods for swapping values with default values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::SwapWithDefaultExt;
|
||||
/// let mut s = String::from("abc");
|
||||
/// let old = s.swap_with_default();
|
||||
/// assert_eq!(old, "abc");
|
||||
/// assert_eq!(s, "");
|
||||
/// ```
|
||||
pub trait SwapWithDefaultExt {
|
||||
/// Takes the current value and replaces it with the default value, returning the original.
|
||||
/// Swap with `Self::default()`.
|
||||
fn swap_with_default(&mut self) -> Self;
|
||||
}
|
||||
|
||||
@@ -150,6 +246,26 @@ impl<T: Default> SwapWithDefaultExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides a method to explicitly move values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::rc::Rc;
|
||||
/// use rosenpass_util::mem::MoveExt;
|
||||
/// let val = 42;
|
||||
/// let another_val = val.move_here();
|
||||
/// assert_eq!(another_val, 42);
|
||||
/// // val is now inaccessible
|
||||
///
|
||||
/// let value = Rc::new(42);
|
||||
/// let clone = Rc::clone(&value);
|
||||
///
|
||||
/// assert_eq!(Rc::strong_count(&value), 2);
|
||||
///
|
||||
/// clone.move_here(); // this will drop the second reference
|
||||
///
|
||||
/// assert_eq!(Rc::strong_count(&value), 1);
|
||||
/// ```
|
||||
pub trait MoveExt {
|
||||
/// Deliberately move the value
|
||||
///
|
||||
@@ -163,3 +279,29 @@ impl<T: Sized> MoveExt for T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_forgetting {
|
||||
use crate::mem::Forgetting;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_forgetting() {
|
||||
let drop_was_called = Arc::new(AtomicBool::new(false));
|
||||
struct SetFlagOnDrop(Arc<AtomicBool>);
|
||||
impl Drop for SetFlagOnDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
}
|
||||
drop(SetFlagOnDrop(drop_was_called.clone()));
|
||||
assert!(drop_was_called.load(SeqCst));
|
||||
// reset flag and use Forgetting
|
||||
drop_was_called.store(false, SeqCst);
|
||||
let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone()));
|
||||
drop(forgetting);
|
||||
assert_eq!(drop_was_called.load(SeqCst), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
result::OkExt,
|
||||
};
|
||||
|
||||
/// Module containing I/O interest flags for Unix operations
|
||||
/// Module containing I/O interest flags for Unix operations (see also: [mio::Interest])
|
||||
pub mod interest {
|
||||
use mio::Interest;
|
||||
|
||||
@@ -20,9 +20,48 @@ pub mod interest {
|
||||
pub const RW: Interest = R.add(W);
|
||||
}
|
||||
|
||||
/// Extension trait providing additional functionality for Unix listener
|
||||
/// Extension trait providing additional functionality for a Unix listener
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use mio::net::{UnixListener, UnixStream};
|
||||
/// use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
|
||||
///
|
||||
/// use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// // This would be the UDS created by an external source
|
||||
/// let socket_path = "/tmp/rp_mio_uds_test_socket";
|
||||
/// if Path::new(socket_path).exists() {
|
||||
/// std::fs::remove_file(socket_path).expect("Failed to remove existing socket");
|
||||
/// }
|
||||
///
|
||||
/// // An extended MIO listener can then be created by claiming the existing socket
|
||||
/// // Note that the original descriptor is not reused, but copied before claiming it here
|
||||
/// let listener = UnixListener::bind(socket_path).unwrap();
|
||||
/// let listener_fd: RawFd = listener.as_raw_fd();
|
||||
/// let ext_listener = <UnixListener as UnixListenerExt>
|
||||
/// ::claim_fd(listener_fd).expect("Failed to claim_fd for ext_listener socket");
|
||||
///
|
||||
/// // Similarly, "client" connections can be established by claiming existing sockets
|
||||
/// // Note that in this case, the file descriptor will be reused (safety implications!)
|
||||
/// let stream = UnixStream::connect(socket_path).unwrap();
|
||||
/// let stream_fd = stream.into_raw_fd();
|
||||
/// let ext_stream = <UnixStream as UnixStreamExt>
|
||||
/// ::claim_fd_inplace(stream_fd).expect("Failed to claim_fd_inplace for ext_stream socket");
|
||||
///
|
||||
/// // Handle accepted connections...
|
||||
/// ext_listener.accept().expect("Failed to accept incoming connection");
|
||||
///
|
||||
/// // Send or receive messages ...
|
||||
///
|
||||
/// // Cleanup, shutdown etc. goes here ...
|
||||
/// std::fs::remove_file(socket_path).unwrap();
|
||||
/// ```
|
||||
pub trait UnixListenerExt: Sized {
|
||||
/// Creates a new Unix listener by claiming ownership of a raw file descriptor
|
||||
/// (see [fd::claim_fd](crate::fd::claim_fd))
|
||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
@@ -36,15 +75,17 @@ impl UnixListenerExt for UnixListener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait providing additional functionality for Unix streams
|
||||
/// Extension trait providing additional functionality for a Unix stream
|
||||
pub trait UnixStreamExt: Sized {
|
||||
/// Creates a new Unix stream from an owned file descriptor
|
||||
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
|
||||
|
||||
/// Claims ownership of a raw file descriptor and creates a new Unix stream
|
||||
/// (see [fd::claim_fd](crate::fd::claim_fd))
|
||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||
|
||||
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
|
||||
/// (see [fd::claim_fd_inplace](crate::fd::claim_fd_inplace))
|
||||
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,58 @@ 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.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::collections::VecDeque;
|
||||
/// use std::io::Cursor;
|
||||
/// use std::io::Read;
|
||||
/// use std::os::fd::AsRawFd;
|
||||
/// use std::os::fd::OwnedFd;
|
||||
///
|
||||
/// use mio::net::UnixStream;
|
||||
/// use rosenpass_util::mio::ReadWithFileDescriptors;
|
||||
/// use rosenpass_util::io::TryIoResultKindHintExt;
|
||||
///
|
||||
/// const MAX_REQUEST_FDS : usize = 2; // Limit to 2 descriptors per read operation
|
||||
/// let mut read_fd_buffer = VecDeque::<OwnedFd>::new(); // File descriptor queue
|
||||
///
|
||||
/// // In this case, the unused writable end of the connection can be ignored
|
||||
/// let (io_stream, _) = UnixStream::pair().expect("failed to create socket pair");
|
||||
///
|
||||
/// // Wait until the output stream is writable...
|
||||
///
|
||||
/// // Wrap the socket to start tracking received file descriptors
|
||||
/// let mut fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
|
||||
/// &io_stream,
|
||||
/// &mut read_fd_buffer,
|
||||
/// );
|
||||
////
|
||||
/// // Simulated reads; the actual operations will depend on the protocol (implementation details)
|
||||
/// let mut recv_buffer = Vec::<u8>::new();
|
||||
/// let bytes_read = fd_passing_sock.read(&mut recv_buffer[..]).expect("error reading from socket");
|
||||
/// assert_eq!(bytes_read, 0);
|
||||
/// assert_eq!(&recv_buffer[..bytes_read], []);
|
||||
///
|
||||
/// // Alternatively, it's possible to use the try_io_err_kind_hint utility provided by this crate
|
||||
/// match fd_passing_sock.read(&mut recv_buffer).try_io_err_kind_hint() {
|
||||
/// Err(_) => {
|
||||
/// // Handle errors here ...
|
||||
/// }
|
||||
/// Ok(result) => {
|
||||
/// // Process messages here ...
|
||||
/// assert_eq!(0, result); // Nothing to read in this example
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// // The wrapped components can still be accessed
|
||||
/// assert_eq!(fd_passing_sock.socket().as_raw_fd(), io_stream.as_raw_fd());
|
||||
/// let (socket, fd_queue) = fd_passing_sock.into_parts();
|
||||
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
|
||||
///
|
||||
/// // Shutdown, cleanup, etc. goes here ...
|
||||
/// ```
|
||||
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
|
||||
where
|
||||
Sock: FdPassingExt,
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
/// Try block basically…returns a result and allows the use of the question mark operator inside
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use anyhow::Result;
|
||||
/// # use rosenpass_util::attempt;
|
||||
/// let result: Result<i32> = attempt!({
|
||||
/// let x = 42;
|
||||
/// Ok(x)
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(result.unwrap(), 42);
|
||||
///
|
||||
/// let error_result: Result<()> = attempt!({
|
||||
/// Err(anyhow::anyhow!("some error"))
|
||||
/// });
|
||||
///
|
||||
/// assert!(error_result.is_err());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! attempt {
|
||||
($block:expr) => {
|
||||
@@ -9,6 +27,19 @@ macro_rules! attempt {
|
||||
}
|
||||
|
||||
/// Trait for the ok operation, which provides a way to convert a value into a Result
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::result::OkExt;
|
||||
/// let value: i32 = 42;
|
||||
/// let result: Result<i32, &str> = value.ok();
|
||||
///
|
||||
/// assert_eq!(result, Ok(42));
|
||||
///
|
||||
/// let value = "hello";
|
||||
/// let result: Result<&str, &str> = value.ok();
|
||||
///
|
||||
/// assert_eq!(result, Ok("hello"));
|
||||
/// ```
|
||||
pub trait OkExt<E>: Sized {
|
||||
/// Wraps a value in a Result::Ok variant
|
||||
fn ok(self) -> Result<Self, E>;
|
||||
@@ -26,6 +57,11 @@ impl<T, E> OkExt<E> for T {
|
||||
/// the function will not panic.
|
||||
///
|
||||
/// Implementations must not panic.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use rosenpass_util::result::GuaranteedValue;
|
||||
/// let x:u32 = 10u8.try_into().guaranteed();
|
||||
/// ```
|
||||
pub trait GuaranteedValue {
|
||||
/// The value type that will be returned by guaranteed()
|
||||
type Value;
|
||||
|
||||
@@ -3,7 +3,23 @@ use typenum::int::{NInt, PInt, Z0};
|
||||
use typenum::marker_traits as markers;
|
||||
use typenum::uint::{UInt, UTerm};
|
||||
|
||||
/// Convenience macro to convert type numbers to constant integers
|
||||
/// Convenience macro to convert [`typenum`] type numbers to constant integers.
|
||||
///
|
||||
/// This macro takes a [`typenum`] type-level integer (like `U5`, `P3`, or `N7`)
|
||||
/// and converts it into its equivalent constant integer value at compile time.
|
||||
/// By default, it converts to a suitable unsigned integer type, but you can
|
||||
/// specify a target type explicitly using `typenum2const!(Type as i32)`,
|
||||
/// for example.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use typenum::consts::U10;
|
||||
/// # use rosenpass_util::typenum2const;
|
||||
///
|
||||
/// const TEN: u32 = typenum2const!(U10 as u32);
|
||||
/// assert_eq!(TEN, 10);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! typenum2const {
|
||||
($val:ty) => {
|
||||
@@ -14,35 +30,80 @@ macro_rules! typenum2const {
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait implemented by constant integers to facilitate conversion to constant integers
|
||||
/// A trait implemented by type-level integers to facilitate their conversion
|
||||
/// into constant values.
|
||||
///
|
||||
/// Types from the [`typenum`] crate (like `U5`, `P3`, or `N7`) can implement
|
||||
/// `IntoConst` to produce a compile-time constant integer of the specified
|
||||
/// type. This trait is part of the underlying mechanism used by the
|
||||
/// [`crate::typenum2const`] macro.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::typenum2const;
|
||||
/// use typenum::consts::U42;
|
||||
/// use rosenpass_util::typenum::IntoConst;
|
||||
///
|
||||
/// // Directly using IntoConst:
|
||||
/// const VALUE: u64 = <U42 as IntoConst<u64>>::VALUE;
|
||||
/// assert_eq!(VALUE, 42);
|
||||
///
|
||||
/// // Or via the macro:
|
||||
/// const VALUE_MACRO: u64 = typenum2const!(U42 as u64);
|
||||
/// assert_eq!(VALUE_MACRO, 42);
|
||||
/// ```
|
||||
pub trait IntoConst<T> {
|
||||
/// The constant value after conversion
|
||||
/// The constant value after conversion.
|
||||
const VALUE: T;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Internal struct for applying a negative sign to an unsigned type-level integer during conversion.
|
||||
///
|
||||
/// This is part of the implementation detail for signed conversions. It uses
|
||||
/// [`AssociatedUnsigned`] to determine the underlying unsigned type and negates its value.
|
||||
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
|
||||
*const T,
|
||||
*const Param,
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Internal struct for applying a positive sign to an unsigned type-level integer during conversion.
|
||||
///
|
||||
/// This is used as part of converting a positive signed type-level integer to its runtime integer
|
||||
/// value, ensuring that the correct unsigned representation is known via [`AssociatedUnsigned`].
|
||||
struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
|
||||
*const T,
|
||||
*const Param,
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
|
||||
/// Internal struct representing a left-shift operation on a type-level integer.
|
||||
///
|
||||
/// Used as part of compile-time computations. `SHIFT` determines how many bits the value will be
|
||||
/// shifted to the left.
|
||||
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param);
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
|
||||
/// Internal struct representing an addition operation between two type-level integers.
|
||||
///
|
||||
/// `ConstAdd` is another building block for compile-time arithmetic on type-level integers before
|
||||
/// their conversion to runtime constants.
|
||||
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs);
|
||||
|
||||
/// Assigns an unsigned type to a signed type
|
||||
/// Associates an unsigned type with a signed type, enabling conversions between signed and unsigned
|
||||
/// representations of compile-time integers.
|
||||
///
|
||||
/// This trait is used internally to facilitate the conversion of signed [`typenum`] integers by
|
||||
/// referencing their underlying unsigned representation.
|
||||
trait AssociatedUnsigned {
|
||||
/// The associated unsigned type.
|
||||
type Type;
|
||||
}
|
||||
|
||||
/// Internal macro implementing the [`IntoConst`] trait for a given mapping from a type-level integer
|
||||
/// to a concrete integer type.
|
||||
macro_rules! impl_into_const {
|
||||
( $from:ty as $to:ty := $impl:expr) => {
|
||||
impl IntoConst<$to> for $from {
|
||||
@@ -51,6 +112,10 @@ macro_rules! impl_into_const {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing common `IntoConst` logic for various numeric types.
|
||||
///
|
||||
/// It sets up `Z0`, `B0`, `B1`, `UTerm`, and also provides default implementations for
|
||||
/// `ConstLshift` and `ConstAdd`.
|
||||
macro_rules! impl_numeric_into_const_common {
|
||||
($type:ty) => {
|
||||
impl_into_const! { Z0 as $type := 0 }
|
||||
@@ -73,6 +138,10 @@ macro_rules! impl_numeric_into_const_common {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing `IntoConst` for unsigned integer types.
|
||||
///
|
||||
/// It sets up conversions for multiple unsigned integer target types and
|
||||
/// provides the positive sign application implementation.
|
||||
macro_rules! impl_numeric_into_const_unsigned {
|
||||
($($to_list:ty),*) => {
|
||||
$( impl_numeric_into_const_unsigned! { @impl $to_list } )*
|
||||
@@ -91,6 +160,9 @@ macro_rules! impl_numeric_into_const_unsigned {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing `IntoConst` for signed integer types.
|
||||
///
|
||||
/// It uses their associated unsigned types to handle positive and negative conversions correctly.
|
||||
macro_rules! impl_numeric_into_const_signed {
|
||||
($($to_list:ty : $unsigned_list:ty),*) => {
|
||||
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )*
|
||||
@@ -110,9 +182,8 @@ macro_rules! impl_numeric_into_const_signed {
|
||||
impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyNegSign<$type, Param> {
|
||||
const VALUE : $type =
|
||||
if Param::VALUE == (1 as $unsigned).rotate_right(1) {
|
||||
// Handle the negative value without an associated positive value (e.g. -128
|
||||
// for i8)
|
||||
< $type >::MIN
|
||||
// Handling negative values at boundaries, such as i8::MIN
|
||||
<$type>::MIN
|
||||
} else {
|
||||
-(Param::VALUE as $type)
|
||||
};
|
||||
@@ -122,10 +193,10 @@ macro_rules! impl_numeric_into_const_signed {
|
||||
|
||||
impl_into_const! { B0 as bool := false }
|
||||
impl_into_const! { B1 as bool := true }
|
||||
|
||||
impl_numeric_into_const_unsigned! { usize, u8, u16, u32, u64, u128 }
|
||||
impl_numeric_into_const_signed! { isize : usize, i8 : u8, i16 : u16, i32 : u32, i64 : u64, i128 : u128 }
|
||||
|
||||
// Unsigned type numbers to const integers
|
||||
impl<Ret, Rest, Bit> IntoConst<Ret> for UInt<Rest, Bit>
|
||||
where
|
||||
Rest: IntoConst<Ret>,
|
||||
@@ -133,26 +204,28 @@ where
|
||||
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
|
||||
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts an unsigned [`UInt`] typenum into its corresponding constant integer by
|
||||
/// decomposing it into shifts and additions on its subparts.
|
||||
const VALUE: Ret = <ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
// Signed type numbers with positive sign to const integers
|
||||
impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
|
||||
where
|
||||
Ret: AssociatedUnsigned,
|
||||
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
|
||||
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts a positive signed [`PInt`] typenum into its corresponding constant integer.
|
||||
const VALUE: Ret = <ConstApplyPosSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
// Signed type numbers with negative sign to const integers
|
||||
impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
|
||||
where
|
||||
Ret: AssociatedUnsigned,
|
||||
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
|
||||
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts a negative signed [`NInt`] typenum into its corresponding constant integer.
|
||||
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,23 @@
|
||||
//!
|
||||
//! This module provides an extension trait,
|
||||
//! [`ZeroizedExt`](crate::zeroize::ZeroizedExt), for all types implementing the
|
||||
//! `zeroize::Zeroize` trait.
|
||||
//! It introduces the [`zeroized`](crate::zeroize::ZeroizedExt::zeroized)
|
||||
//! method, which zeroizes a value in place and returns it, making it convenient
|
||||
//! for chaining operations and ensuring sensitive data is securely erased.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use zeroize::Zeroize;
|
||||
//! use rosenpass_util::zeroize::ZeroizedExt;
|
||||
//!
|
||||
//! let mut value = String::from("hello");
|
||||
//! value.zeroize(); // Zeroizes in place
|
||||
//! assert_eq!(value, "");
|
||||
//!
|
||||
//! assert_eq!(String::from("hello").zeroized(), "");
|
||||
//! ```
|
||||
|
||||
mod zeroized_ext;
|
||||
pub use zeroized_ext::*;
|
||||
|
||||
Reference in New Issue
Block a user