Compare commits

...

18 Commits

Author SHA1 Message Date
Karolin Varner
caf91c84f0 chore: Remove unused warning in api integration test 2024-12-18 13:45:38 +01:00
Karolin Varner
f5b4c17011 fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 13:45:38 +01:00
Karolin Varner
12506e5f95 chore: Final improvements on the to crate API doc 2024-12-18 13:45:38 +01:00
David Niehues
965600212d docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-18 13:45:38 +01:00
Paul Spooren
d807a1bca7 Add examples and docstring improvements for mio/uds_recv_fd (#551)
Some checks are pending
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run
2024-12-18 12:29:20 +01:00
Karolin Varner
4daf97b2ee style(ciphers): improve style in doc-tests around using the the ?-operator in the ciphers crate (#549) 2024-12-18 11:23:59 +01:00
Karolin Varner
b394e302ab chore(tests): start using unused test output (#547) 2024-12-18 11:22:38 +01:00
Paul Spooren
198bc2d5f2 chore(tests): start using unused test output
Resolve a warning of unused `output` variable.

Fixes: 0745019 docs(cli): Create commented config file

Signed-off-by: Paul Spooren <mail@aparcar.org>
2024-12-18 11:22:16 +01:00
Paul Spooren
fc2f535eae docs(util): add docs and examples for the remaining util crate (#545) 2024-12-18 11:16:00 +01:00
Paul Spooren
302e249f08 docs(constant-time): add docs, examples and safety notices (#544) 2024-12-18 10:58:35 +01:00
dependabot[bot]
d8fe3eba5f build(deps): bump clap_complete from 4.5.38 to 4.5.40
Bumps [clap_complete](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.40.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.40)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 10:29:44 +01:00
David Niehues
61b8b28e86 style(ciphers): improve style in doc-tests around using the the ?-operator in the ciphers crate 2024-12-17 11:57:54 +01:00
Amin Faez
26f77924f8 docs(constant-time): add docs, examples and safety notices 2024-12-17 11:56:33 +01:00
Amin Faez
2e0e2cfa0c docs(util): add docs and examples for the remaining util crate 2024-12-17 11:55:23 +01:00
Philipp Dresselmann
a537eb3e1b chore(docs): Adjust docstrings for the mio module 2024-12-16 22:29:01 +01:00
Philipp Dresselmann
ea233bf137 chore(docs): Add an example for the UnixListenerExt trait 2024-12-16 22:28:53 +01:00
Philipp Dresselmann
db8796ab40 chore(docs): Add an example for the uds_recv_fd module 2024-12-16 20:54:08 +01:00
Philipp Dresselmann
51d4dede15 chore(doc): Add a link to the MIO utils module summary 2024-12-16 17:02:43 +01:00
33 changed files with 1046 additions and 110 deletions

4
Cargo.lock generated
View File

@@ -403,9 +403,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.38" version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
dependencies = [ dependencies = [
"clap 4.5.23", "clap 4.5.23",
] ]

View File

@@ -49,7 +49,7 @@ typenum = "1.17.0"
log = { version = "0.4.22" } log = { version = "0.4.22" }
clap = { version = "4.5.23", features = ["derive"] } clap = { version = "4.5.23", features = ["derive"] }
clap_mangen = "0.2.24" clap_mangen = "0.2.24"
clap_complete = "4.5.38" clap_complete = "4.5.40"
serde = { version = "1.0.216", features = ["derive"] } serde = { version = "1.0.216", features = ["derive"] }
arbitrary = { version = "1.4.1", features = ["derive"] } arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.94", features = ["backtrace", "std"] } anyhow = { version = "1.0.94", features = ["backtrace", "std"] }

View File

@@ -13,7 +13,6 @@ pub use hash::KEY_LEN;
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets(); /// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
/// ///
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER"; /// 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 /// // create use once hash domain for the protocol identifier
/// let mut hash_domain = HashDomain::zero(); /// let mut hash_domain = HashDomain::zero();
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?; /// 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_identifier = "my_new_key_identifier".as_bytes();
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret(); /// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
/// ///
/// # Ok(()) /// # Ok::<(), anyhow::Error>(())
/// # }
/// # do_doc_test().unwrap();
///
///``` ///```
/// ///

View File

@@ -20,3 +20,6 @@ memsec = { workspace = true }
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" rand = "0.8.5"
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -2,14 +2,29 @@
use core::ptr; use core::ptr;
/// Little endian memcmp version of quinier/memsec /// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30)
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
/// ///
/// # Panic & Safety /// # Panic & Safety
/// ///
/// Both input arrays must be at least of the indicated length. /// Both input arrays must be at least of the indicated length.
/// ///
/// See [std::ptr::read_volatile] on safety. /// 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)] #[inline(never)]
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 { pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
let mut res = 0; let mut res = 0;
@@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len()); assert!(a.len() == b.len());
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.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);
}
}
}

View File

@@ -6,8 +6,16 @@ use core::hint::black_box;
/// and increment that integer. /// and increment that integer.
/// ///
/// # Leaks /// # Leaks
/// TODO: mention here if this function leaks any information, see /// This function may leak timing information in the following ways:
/// <https://github.com/rosenpass/rosenpass/issues/232> ///
/// - 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 /// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see /// For discussion on how to ensure the constant-time execution of this function, see

View File

@@ -7,6 +7,32 @@
//! ## TODO //! ## TODO
//! Figure out methodology to ensure that code is actually constant time, see //! Figure out methodology to ensure that code is actually constant time, see
//! <https://github.com/rosenpass/rosenpass/issues/232> //! <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 compare;
mod increment; mod increment;
@@ -14,6 +40,7 @@ mod memcmp;
mod xor; mod xor;
pub use compare::compare; pub use compare::compare;
pub use compare::memcmp_le;
pub use increment::increment; pub use increment::increment;
pub use memcmp::memcmp; pub use memcmp::memcmp;
pub use xor::xor; pub use xor::xor;

View File

@@ -113,9 +113,10 @@ mod tests {
// Pearson correlation // Pearson correlation
let correlation = cv / (sd_x * sd_y); let correlation = cv / (sd_x * sd_y);
println!("correlation: {:.6?}", correlation); println!("correlation: {:.6?}", correlation);
#[cfg(not(coverage))]
assert!( assert!(
correlation.abs() < 0.01, correlation.abs() < 0.01,
"execution time correlates with result" "execution time correlates with result"
) );
} }
} }

View File

@@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To};
/// Xors the source into the destination /// 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 /// # Panics
/// If source and destination are of different sizes. /// If source and destination are of different sizes.
/// ///
/// # Leaks /// # Leaks
/// TODO: mention here if this function leaks any information, see /// This function may leak timing information in the following ways:
/// <https://github.com/rosenpass/rosenpass/issues/232> ///
/// - 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 /// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see /// For discussion on how to ensure the constant-time execution of this function, see

View File

@@ -91,3 +91,6 @@ experiment_api = [
internal_signal_handling_for_coverage_reports = ["signal-hook"] internal_signal_handling_for_coverage_reports = ["signal-hook"]
internal_testing = [] internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"] internal_bin_gen_ipc_msg_types = ["hex", "heck"]
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -39,7 +39,7 @@ impl Drop for KillChild {
// system is a bit broken; there is probably a few functions that just restart on EINTR // system is a bit broken; there is probably a few functions that just restart on EINTR
// so the signal is absorbed // so the signal is absorbed
loop { loop {
rustix::process::kill_process(pid, Term).discard_result(); kill_process(pid, Term).discard_result();
if self.0.try_wait().unwrap().is_some() { if self.0.try_wait().unwrap().is_some() {
break; break;
} }

View File

@@ -160,6 +160,9 @@ fn check_example_config() {
.output() .output()
.expect("EXAMPLE_CONFIG not valid"); .expect("EXAMPLE_CONFIG not valid");
let stderr = String::from_utf8_lossy(&output.stderr);
assert_eq!(stderr, "");
fs::copy( fs::copy(
tmp_dir.path().join("rp-public-key"), tmp_dir.path().join("rp-public-key"),
tmp_dir.path().join("rp-peer-public-key"), tmp_dir.path().join("rp-peer-public-key"),

View File

@@ -2,11 +2,9 @@
use std::{ use std::{
borrow::{Borrow, BorrowMut}, borrow::{Borrow, BorrowMut},
collections::VecDeque, collections::VecDeque,
fmt::{Debug, Write}, ops::DerefMut,
ops::{DerefMut, RangeBounds},
}; };
use rand::distributions::uniform::SampleBorrow;
use rosenpass_cipher_traits::Kem; use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem; use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::result::OkExt; use rosenpass_util::result::OkExt;
@@ -28,48 +26,53 @@ fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
sim.poll_loop(150)?; // Poll 75 times sim.poll_loop(150)?; // Poll 75 times
let transcript = sim.transcript; let transcript = sim.transcript;
let completions: Vec<_> = transcript let _completions: Vec<_> = transcript
.iter() .iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_)))) .filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect(); .collect();
#[cfg(not(coverage))]
assert!( assert!(
!completions.is_empty(), !_completions.is_empty(),
"\ "\
Should have performed a successful key exchanged!\n\ Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( assert!(
completions[0].0 < 20.0, _completions[0].0 < 20.0,
"\ "\
First key exchange should happen in under twenty seconds!\n\ First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( assert!(
completions.len() >= 3, _completions.len() >= 3,
"\ "\
Should have at least two renegotiations!\n\ Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( 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\ First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\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\ First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
"); ");
Ok(()) Ok(())
@@ -106,48 +109,53 @@ fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
} }
let transcript = sim.transcript; let transcript = sim.transcript;
let completions: Vec<_> = transcript let _completions: Vec<_> = transcript
.iter() .iter()
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_)))) .filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
.collect(); .collect();
#[cfg(not(coverage))]
assert!( assert!(
!completions.is_empty(), !_completions.is_empty(),
"\ "\
Should have performed a successful key exchanged!\n\ Should have performed a successful key exchanged!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( assert!(
completions[0].0 < 10.0, _completions[0].0 < 10.0,
"\ "\
First key exchange should happen in under twenty seconds!\n\ First key exchange should happen in under twenty seconds!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( assert!(
completions.len() >= 3, _completions.len() >= 3,
"\ "\
Should have at least two renegotiations!\n\ Should have at least two renegotiations!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
" "
); );
#[cfg(not(coverage))]
assert!( 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\ First renegotiation should happen in between two and three minutes!\n\
Transcript: {transcript:?}\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\ First renegotiation should happen in between two and three minutes after the first renegotiation!\n\
Transcript: {transcript:?}\n\ Transcript: {transcript:?}\n\
Completions: {completions:?}\ Completions: {_completions:?}\
"); ");
Ok(()) Ok(())

View File

@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign; use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait. // 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 // be without destination parameters
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where where
T: BitXorAssign + Clone, T: BitXorAssign + Clone,
{ {
// Custom implementations of the to trait can be created, but the easiest // 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]| { with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) { for (d, s) in dst.iter_mut().zip(src.iter()) {

View File

@@ -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}; use crate::{with_destination, To};
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
/// # Panics /// # Panics
/// ///
/// This function will panic if the two slices have different lengths. /// 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], ()> + '_ pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
@@ -23,6 +35,19 @@ where
/// # Panics /// # Panics
/// ///
/// This function will panic if destination is shorter than origin. /// 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], ()> + '_ pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
@@ -34,6 +59,18 @@ where
/// destination. /// destination.
/// ///
/// Copies as much data as is present in the shorter slice. /// 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], ()> + '_ pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
where where
T: Copy, T: Copy,
@@ -47,6 +84,24 @@ where
/// Function with destination that attempts to copy data from origin into the destination. /// Function with destination that attempts to copy data from origin into the destination.
/// ///
/// Will return None if the slices are of different lengths. /// 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<()>> + '_ pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where where
T: Copy, T: Copy,
@@ -62,6 +117,26 @@ where
/// Destination may be longer than origin. /// Destination may be longer than origin.
/// ///
/// Will return None if the destination is shorter 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<()>> + '_ pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
where where
T: Copy, T: Copy,
@@ -72,6 +147,18 @@ where
} }
/// Function with destination that copies all data between two array references. /// 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], ()> + '_ pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
where where
T: Copy, T: Copy,

View File

@@ -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; 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)] #[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
pub struct Beside<Val, Ret>(pub Val, pub Ret); pub struct Beside<Val, Ret>(pub Val, pub Ret);
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
&mut self.1 &mut self.1
} }
/// Perform beside condensation. See [CondenseBeside] /// Perform beside condensation. See [CondenseBeside] for more details.
/// ///
/// # Example /// # Example
/// ``` /// ```
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
(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);
}
}

View File

@@ -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), /// 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 /// [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 /// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
/// condense trait. /// 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> { pub trait CondenseBeside<Val> {
/// The type that results from condensation. /// The type that results from condensation.
type Condensed; type Condensed;
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
impl<Val> CondenseBeside<Val> for () { impl<Val> CondenseBeside<Val> for () {
type Condensed = Val; type Condensed = Val;
/// Replaces this empty tuple with `ret`.
fn condense(self, ret: Val) -> Val { fn condense(self, ret: Val) -> Val {
ret ret
} }
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> { impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
type Condensed = Result<Val, Error>; type Condensed = Result<Val, Error>;
/// Replaces the empty tuple inside this [Result] with `ret`.
fn condense(self, ret: Val) -> Result<Val, Error> { fn condense(self, ret: Val) -> Result<Val, Error> {
self.map(|()| ret) self.map(|()| ret)
} }
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
impl<Val> CondenseBeside<Val> for Option<()> { impl<Val> CondenseBeside<Val> for Option<()> {
type Condensed = Option<Val>; type Condensed = Option<Val>;
/// Replaces the empty tuple inside this [Option] with `ret`.
fn condense(self, ret: Val) -> Option<Val> { fn condense(self, ret: Val) -> Option<Val> {
self.map(|()| ret) self.map(|()| ret)
} }

View File

@@ -1,5 +1,28 @@
/// Helper performing explicit unsized coercion. //! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
/// Used by the [to](crate::to()) function. //! 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> { pub trait DstCoercion<Dst: ?Sized> {
/// Performs an explicit coercion to the destination type. /// Performs an explicit coercion to the destination type.
fn coerce_dest(&mut self) -> &mut Dst; fn coerce_dest(&mut self) -> &mut Dst;

View File

@@ -6,11 +6,16 @@
//! - `Dst: ?Sized`; (e.g. [u8]) The target to write to //! - `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 //! - `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 //! - `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]`). //! `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 //! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as `Dst` //! ordinary return value of a function with an output
//! - `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`). //! - `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 beside;
pub mod condense; pub mod condense;

View File

@@ -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}; use crate::{DstCoercion, To};
/// Alias for [To::to] moving the destination to the left. /// Alias for [To::to] moving the destination to the left.
/// ///
/// This provides similar haptics to the let assignment syntax is rust, which also keeps /// 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. /// 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 pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
where where
Coercable: ?Sized + DstCoercion<Dst>, Coercable: ?Sized + DstCoercion<Dst>,

View File

@@ -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 crate::{Beside, CondenseBeside};
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
/// The To trait is the core of the to crate; most functions with destinations will either return /// The To trait is the core of the to crate; most functions with destinations will either return
/// an object that is an instance of this trait or they will return `-> impl To<Destination, /// an object that is an instance of this trait, or they will return `-> impl To<Destination,
/// Return_value`. /// Return_value>`.
/// ///
/// A quick way to implement a function with destination is to use the /// A quick way to implement a function with destination is to use the
/// [with_destination(|param: &mut Type| ...)] higher order function. /// [with_destination(|param: &mut Type| ...)](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 { pub trait To<Dst: ?Sized, Ret>: Sized {
/// Writes self to the destination `out` and returns a value of type `Ret`. /// 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 /// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return /// returns a [Beside] instance containing the generated destination value and the return
/// value. /// 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> fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
where where
Val: BorrowMut<Dst>, Val: BorrowMut<Dst>,
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// Generate a destination on the fly using default. /// Generate a destination on the fly using default.
/// ///
/// Uses [Default] to create a value, /// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
/// calls [crate::to()] to evaluate the function and finally
/// returns a [Beside] instance containing the generated destination value and the return /// returns a [Beside] instance containing the generated destination value and the return
/// value. /// 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> fn to_value_beside(self) -> Beside<Dst, Ret>
where where
Dst: Sized + Default, Dst: Sized + Default,
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// when the Destination is unsized. /// when the Destination is unsized.
/// ///
/// This could be the case when the destination is an `[u8]` for instance. /// 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> fn collect_beside<Val>(self) -> Beside<Val, Ret>
where where
Val: Default + BorrowMut<Dst>, Val: Default + BorrowMut<Dst>,
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one. /// return value into one.
/// ///
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense]. /// 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 fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
where where
Ret: CondenseBeside<Val>, Ret: CondenseBeside<Val>,
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one. /// return value into one.
/// ///
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense]. /// 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 fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
where where
Dst: Sized + Default, Dst: Sized + Default,
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one. /// return value into one.
/// ///
/// This is like using [Self::collect_beside] followed by calling [Beside::condense]. /// 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 fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
where where
Val: Default + BorrowMut<Dst>, Val: Default + BorrowMut<Dst>,

View File

@@ -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 crate::To;
use std::marker::PhantomData; 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` /// 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 /// # Type Parameters
/// * `Dst` - The destination type the closure operates on /// * `Dst` - The destination type the closure operates on.
/// * `Ret` - The return type of the closure /// * `Ret` - The return type of the closure.
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret` /// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
struct ToClosure<Dst, Ret, Fun> struct ToClosure<Dst, Ret, Fun>
where where
Dst: ?Sized, Dst: ?Sized,
@@ -17,11 +21,11 @@ where
{ {
/// The function to call. /// The function to call.
fun: Fun, fun: Fun,
/// Phantom data to hold the destination type /// Phantom data to hold the destination type.
_val: PhantomData<Box<Dst>>, _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. /// This enables calling the wrapped closure with a destination reference.
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun> impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
@@ -33,6 +37,7 @@ where
/// ///
/// # Arguments /// # Arguments
/// * `out` - Mutable reference to the destination /// * `out` - Mutable reference to the destination
/// See the tutorial in [readme.md] for examples and more explanations.
fn to(self, out: &mut Dst) -> Ret { fn to(self, out: &mut Dst) -> Ret {
(self.fun)(out) (self.fun)(out)
} }
@@ -48,7 +53,24 @@ where
/// * `Ret` - The return type of the closure /// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret` /// * `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> pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where where
Dst: ?Sized, Dst: ?Sized,

View File

@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
/// A type that can be incrementally built from a type that can [Build] it /// 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], /// This is similar to an option, where [Self::Void] is [std::option::Option::None],
/// [Self::Product] is [std::Option::Some], except that there is a third /// [Self::Product] is [std::option::Option::Some], except that there is a third
/// intermediate state [Self::Builder] that represents a Some/Product value /// intermediate state [Self::Builder] that represents a Some/Product value
/// in the process of being made. /// in the process of being made.
/// ///
@@ -508,9 +508,9 @@ where
matches!(self, Self::Void) 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 /// # Examples
/// ///
@@ -541,9 +541,10 @@ where
matches!(self, Self::Builder(..)) 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 /// # Examples
/// ///

View File

@@ -1,4 +1,4 @@
/// A collection of control flow utility macros //! A collection of control flow utility macros
#[macro_export] #[macro_export]
/// A simple for loop to repeat a $body a number of times /// A simple for loop to repeat a $body a number of times
@@ -33,7 +33,7 @@ macro_rules! repeat {
/// 0 /// 0
/// } /// }
/// assert_eq!(test_fn(), 0); /// assert_eq!(test_fn(), 0);
///
/// fn test_fn2() -> i32 { /// fn test_fn2() -> i32 {
/// return_unless!(false, 1); /// return_unless!(false, 1);
/// 0 /// 0
@@ -65,7 +65,7 @@ macro_rules! return_unless {
/// 0 /// 0
/// } /// }
/// assert_eq!(test_fn(), 1); /// assert_eq!(test_fn(), 1);
///
/// fn test_fn2() -> i32 { /// fn test_fn2() -> i32 {
/// return_if!(false, 1); /// return_if!(false, 1);
/// 0 /// 0
@@ -98,7 +98,7 @@ macro_rules! return_if {
/// sum += 1; /// sum += 1;
/// } /// }
/// assert_eq!(sum, 5); /// assert_eq!(sum, 5);
///
/// let mut sum = 0; /// let mut sum = 0;
/// 'one: for _ in 0..10 { /// 'one: for _ in 0..10 {
/// for j in 0..20 { /// for j in 0..20 {
@@ -134,7 +134,7 @@ macro_rules! break_if {
/// sum += 1; /// sum += 1;
/// } /// }
/// assert_eq!(sum, 9); /// assert_eq!(sum, 9);
///
/// let mut sum = 0; /// let mut sum = 0;
/// 'one: for i in 0..10 { /// 'one: for i in 0..10 {
/// continue_if!(i == 5, 'one); /// continue_if!(i == 5, 'one);

View File

@@ -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 /// Will panic if the given file descriptor is negative of or larger than
/// the file descriptor numbers permitted by the operating system. /// 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<()> { pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting, // 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 // 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")] #[cfg(target_os = "linux")]
pub trait GetSocketDomain { pub trait GetSocketDomain {
/// Error type returned by operations in this trait /// Error type returned by operations in this trait
type Error; type Error;
/// Retrieve the socket domain (address family) /// Retrieve the socket domain (address family)
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>; 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> { fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
self.socket_domain() self.socket_domain()
} }
@@ -320,9 +344,67 @@ where
pub trait GetUnixSocketType { pub trait GetUnixSocketType {
/// Error type returned by operations in this trait /// Error type returned by operations in this trait
type Error; 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>; fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
/// Returns Ok(()) only if the underlying socket is a unix stream socket /// 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<()>; fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
} }
@@ -352,16 +434,65 @@ where
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
/// Distinguish between different network socket protocols (e.g. tcp, udp) /// Distinguish between different network socket protocols (e.g. tcp, udp)
pub trait GetSocketProtocol { 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>; fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
/// Check if the socket is a udp socket /// 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> { fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
self.socket_protocol()? self.socket_protocol()?
.map(|p| p == rustix::net::ipproto::UDP) .map(|p| p == rustix::net::ipproto::UDP)
.unwrap_or(false) .unwrap_or(false)
.ok() .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<()> { fn demand_udp_socket(&self) -> anyhow::Result<()> {
match self.socket_protocol() { match self.socket_protocol() {
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()), Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),

View File

@@ -244,7 +244,6 @@
use std::{borrow::Borrow, io}; use std::{borrow::Borrow, io};
use anyhow::ensure; use anyhow::ensure;
use zerocopy::AsBytes;
/// Generic trait for accessing [std::io::Error::kind] /// Generic trait for accessing [std::io::Error::kind]
/// ///

View File

@@ -22,7 +22,7 @@ pub mod io;
pub mod length_prefix_encoding; pub mod length_prefix_encoding;
/// Memory manipulation and allocation utilities. /// Memory manipulation and allocation utilities.
pub mod mem; pub mod mem;
/// MIO integration utilities. /// [MIO (Metal I/O)](https://docs.rs/crate/mio/) integration utilities.
pub mod mio; pub mod mio;
/// Extended Option type functionality. /// Extended Option type functionality.
pub mod option; pub mod option;

View File

@@ -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::borrow::{Borrow, BorrowMut};
use std::cmp::min; use std::cmp::min;
use std::mem::{forget, swap}; use std::mem::{forget, swap};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// Concatenate two byte arrays
// TODO: Zeroize result? // 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_export]
macro_rules! cat { macro_rules! cat {
($len:expr; $($toks:expr),+) => {{ ($len:expr; $($toks:expr),+) => {{
@@ -22,12 +45,37 @@ macro_rules! cat {
} }
// TODO: consistent inout ordering // 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) { pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
dst.borrow_mut().copy_from_slice(src.borrow()); dst.borrow_mut().copy_from_slice(src.borrow());
} }
/// 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) { pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
let src = src.borrow(); let src = src.borrow();
let dst = dst.borrow_mut(); 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]); 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)] #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
pub struct Forgetting<T> { pub struct Forgetting<T> {
value: Option<T>, value: Option<T>,
} }
impl<T> Forgetting<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 { pub fn new(value: T) -> Self {
let value = Some(value); Self { value: Some(value) }
Self { value }
} }
/// Extracts and returns the contained value, consuming self. /// Consume and return the inner value.
pub fn extract(mut self) -> T { pub fn extract(mut self) -> T {
let mut value = None; let mut value = None;
swap(&mut value, &mut self.value); swap(&mut value, &mut self.value);
@@ -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. /// 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 { pub trait DiscardResultExt {
/// Consumes and discards a value without doing anything with it. /// Consumes and discards a value without doing anything with it.
fn discard_result(self); fn discard_result(self);
@@ -107,8 +172,16 @@ impl<T> DiscardResultExt for T {
} }
/// Trait that provides a method to explicitly forget values. /// 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 { pub trait ForgetExt {
/// Consumes and forgets a value, preventing its destructor from running. /// Forget the value.
fn forget(self); fn forget(self);
} }
@@ -119,10 +192,23 @@ impl<T> ForgetExt for T {
} }
/// Extension trait that provides methods for swapping values. /// 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 { 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; 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); 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. /// 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 { 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; 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. /// 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 { pub trait MoveExt {
/// Deliberately move the value /// Deliberately move the value
/// ///
@@ -163,3 +279,29 @@ impl<T: Sized> MoveExt for T {
self 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);
}
}

View File

@@ -6,7 +6,7 @@ use crate::{
result::OkExt, 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 { pub mod interest {
use mio::Interest; use mio::Interest;
@@ -20,9 +20,48 @@ pub mod interest {
pub const RW: Interest = R.add(W); 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 { pub trait UnixListenerExt: Sized {
/// Creates a new Unix listener by claiming ownership of a raw file descriptor /// 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>; 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 { pub trait UnixStreamExt: Sized {
/// Creates a new Unix stream from an owned file descriptor /// Creates a new Unix stream from an owned file descriptor
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>; fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor and creates a new Unix stream /// 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>; fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream /// 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>; fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
} }

View File

@@ -12,6 +12,58 @@ use crate::fd::{claim_fd_inplace, IntoStdioErr};
/// A wrapper around a socket that combines reading from the socket with tracking /// A wrapper around a socket that combines reading from the socket with tracking
/// received file descriptors. Limits the maximum number of file descriptors that /// received file descriptors. Limits the maximum number of file descriptors that
/// can be received in a single read operation via the `MAX_FDS` parameter. /// 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> pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
where where
Sock: FdPassingExt, Sock: FdPassingExt,

View File

@@ -1,6 +1,24 @@
use std::convert::Infallible; use std::convert::Infallible;
/// Try block basically…returns a result and allows the use of the question mark operator inside /// 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_export]
macro_rules! attempt { macro_rules! attempt {
($block:expr) => { ($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 /// 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 { pub trait OkExt<E>: Sized {
/// Wraps a value in a Result::Ok variant /// Wraps a value in a Result::Ok variant
fn ok(self) -> Result<Self, E>; fn ok(self) -> Result<Self, E>;
@@ -26,6 +57,11 @@ impl<T, E> OkExt<E> for T {
/// the function will not panic. /// the function will not panic.
/// ///
/// Implementations must not panic. /// Implementations must not panic.
/// # Examples
/// ```
/// # use rosenpass_util::result::GuaranteedValue;
/// let x:u32 = 10u8.try_into().guaranteed();
/// ```
pub trait GuaranteedValue { pub trait GuaranteedValue {
/// The value type that will be returned by guaranteed() /// The value type that will be returned by guaranteed()
type Value; type Value;

View File

@@ -3,7 +3,23 @@ use typenum::int::{NInt, PInt, Z0};
use typenum::marker_traits as markers; use typenum::marker_traits as markers;
use typenum::uint::{UInt, UTerm}; 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_export]
macro_rules! typenum2const { macro_rules! typenum2const {
($val:ty) => { ($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> { pub trait IntoConst<T> {
/// The constant value after conversion /// The constant value after conversion.
const VALUE: T; const VALUE: T;
} }
#[allow(dead_code)] #[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>>( struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T, *const T,
*const Param, *const Param,
); );
#[allow(dead_code)] #[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>>( struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T, *const T,
*const Param, *const Param,
); );
#[allow(dead_code)] #[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)] #[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 { trait AssociatedUnsigned {
/// The associated unsigned type.
type 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 { macro_rules! impl_into_const {
( $from:ty as $to:ty := $impl:expr) => { ( $from:ty as $to:ty := $impl:expr) => {
impl IntoConst<$to> for $from { 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 { macro_rules! impl_numeric_into_const_common {
($type:ty) => { ($type:ty) => {
impl_into_const! { Z0 as $type := 0 } 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 { macro_rules! impl_numeric_into_const_unsigned {
($($to_list:ty),*) => { ($($to_list:ty),*) => {
$( impl_numeric_into_const_unsigned! { @impl $to_list } )* $( 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 { macro_rules! impl_numeric_into_const_signed {
($($to_list:ty : $unsigned_list:ty),*) => { ($($to_list:ty : $unsigned_list:ty),*) => {
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )* $( 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> { impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyNegSign<$type, Param> {
const VALUE : $type = const VALUE : $type =
if Param::VALUE == (1 as $unsigned).rotate_right(1) { if Param::VALUE == (1 as $unsigned).rotate_right(1) {
// Handle the negative value without an associated positive value (e.g. -128 // Handling negative values at boundaries, such as i8::MIN
// for i8) <$type>::MIN
< $type >::MIN
} else { } else {
-(Param::VALUE as $type) -(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! { B0 as bool := false }
impl_into_const! { B1 as bool := true } impl_into_const! { B1 as bool := true }
impl_numeric_into_const_unsigned! { usize, u8, u16, u32, u64, u128 } 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 } 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> impl<Ret, Rest, Bit> IntoConst<Ret> for UInt<Rest, Bit>
where where
Rest: IntoConst<Ret>, Rest: IntoConst<Ret>,
@@ -133,26 +204,28 @@ where
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>, ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: 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; 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> impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
where where
Ret: AssociatedUnsigned, Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>, Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>, 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; 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> impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
where where
Ret: AssociatedUnsigned, Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>, Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>, 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; const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
} }

View File

@@ -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; mod zeroized_ext;
pub use zeroized_ext::*; pub use zeroized_ext::*;