mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +03:00
Compare commits
18 Commits
dev/karo/d
...
docu-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf91c84f0 | ||
|
|
f5b4c17011 | ||
|
|
12506e5f95 | ||
|
|
965600212d | ||
|
|
d807a1bca7 | ||
|
|
4daf97b2ee | ||
|
|
b394e302ab | ||
|
|
198bc2d5f2 | ||
|
|
fc2f535eae | ||
|
|
302e249f08 | ||
|
|
d8fe3eba5f | ||
|
|
61b8b28e86 | ||
|
|
26f77924f8 | ||
|
|
2e0e2cfa0c | ||
|
|
a537eb3e1b | ||
|
|
ea233bf137 | ||
|
|
db8796ab40 | ||
|
|
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)'] }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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,8 +182,7 @@ 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)
|
||||
// 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