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) 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
36 changed files with 1083 additions and 618 deletions

4
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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"] }

View File

@@ -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>(())
///```
///

View File

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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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"
)
);
}
}

View File

@@ -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

View File

@@ -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)'] }

View File

@@ -1,5 +1,3 @@
/// This contains the bulk of the rosenpass server IO handling code whereas
/// the actual cryptographic code lives in the [crate::protocol] module
use anyhow::bail;
use anyhow::Context;
@@ -51,78 +49,33 @@ use crate::{
use rosenpass_util::attempt;
use rosenpass_util::b64::B64Display;
/// The maximum size of a base64 encoded symmetric key (estimate)
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
/// The maximum size of a base64 peer ID (estimate)
pub const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
/// The zero IPv4 address; this is generally used to tell network servers to choose any interface
/// when listening
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
/// The zero IPv6 address; this is generally used to tell network servers to choose any interface
/// when listening
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
/// Ratio of blocking epoll(7) polls to non-blocking polls at which the rosenpass server
/// assumes it is under load (i.e. a DOS attack may be happening)
const UNDER_LOAD_RATIO: f64 = 0.5;
/// Period at which the DOS detection code updates whether there is an "under load" status
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
pub const BROKER_ID_BYTES: usize = 8;
const BROKER_ID_BYTES: usize = 8;
/// IPv4 address that tells the network layer to listen on any interface
///
/// # Examples
///
/// See [AppServer::new].
pub fn ipv4_any_binding() -> SocketAddr {
fn ipv4_any_binding() -> SocketAddr {
// addr, port
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
}
/// IPv6 address that tells the network layer to listen on any interface
///
/// # Examples
///
/// See [AppServer::new].
pub fn ipv6_any_binding() -> SocketAddr {
fn ipv6_any_binding() -> SocketAddr {
// addr, port, flowinfo, scope_id
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
}
/// This is used to assign indices to MIO (epoll) event sources
#[derive(Debug, Default)]
pub struct MioTokenDispenser {
pub counter: usize,
counter: usize,
}
impl MioTokenDispenser {
/// Produces a single IO event source ID
///
/// # Examples
///
/// Use is quite straightforward:
///
/// ```
/// use rosenpass::app_server::MioTokenDispenser;
/// use mio::Token;
///
/// let mut dispenser = MioTokenDispenser {
/// counter: 0
/// };
///
/// let t1 = dispenser.dispense();
/// let t2 = dispenser.dispense();
///
/// assert_ne!(t1, t2);
///
/// // If you inspected the output, you would find that the dispenser is really just a counter.
/// // Though this is an implementation detail
/// assert_eq!(t1, Token(0));
/// assert_eq!(t2, Token(1));
/// ```
///
pub fn dispense(&mut self) -> Token {
let r = self.counter;
self.counter += 1;
@@ -130,132 +83,42 @@ impl MioTokenDispenser {
}
}
/// List of WireGuard brokers
///
/// Each WireGuard peer ([AppPeer]) is assigned a broker ([AppPeer::broker_peer]).
///
/// When a new has been exchanged, then its associated broker is called to transmit the key
/// to WireGuard (or to do something else).
///
/// Brokers live in [AppServer::brokers]. They are added/removed from the AppServer via
/// [AppServer::register_broker] and [AppServer::unregister_broker]. PSKs are distributed
/// to their respective brokers via [AppPeerPtr::set_psk].
///
/// Note that the entire broker system is an experimental feature; it is not up to the
/// same quality standards as the rest of the code. In particular, the use of [BROKER_ID_BYTES]
/// and a hash map is simultaneously overengineered and not really that useful for what it is
/// doing.
#[derive(Debug, Default)]
pub struct BrokerStore {
/// The collection of WireGuard brokers. See [Self].
pub store: HashMap<
Public<BROKER_ID_BYTES>,
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
>,
}
/// Reference to a broker imbued with utility methods
#[derive(Debug, Clone)]
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
/// This is the broker configuration for a particular broker peer
#[derive(Debug)]
pub struct BrokerPeer {
/// Reference to the broker used for this particular peer
ptr: BrokerStorePtr,
/// Configuration for a WireGuard broker.
///
/// This is woefully overengineered and there is very little reason why the broker
/// configuration should not live in the particular WireGuard broker.
peer_cfg: Box<dyn WireguardBrokerCfg>,
}
impl BrokerPeer {
/// Create a broker peer
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
Self { ptr, peer_cfg }
}
/// Retrieve the pointer to WireGuard PSK broker used with this peer
pub fn ptr(&self) -> &BrokerStorePtr {
&self.ptr
}
}
/// IO/BusinessLogic information for a particular protocol peer.
///
/// There is a one-to-one correspondence between this struct and [crate::protocol::Peer];
/// whereas the struct in the protocol module stores information specific to the cryptographic layer,
/// this struct stores information IO information.
#[derive(Default, Debug)]
pub struct AppPeer {
/// If set, then [AppServer::output_key] will write generated output keys
/// to a file configured here and produce information on standard out to
/// notify the calling process that
pub outfile: Option<PathBuf>,
/// If this option is set, then [AppServer::output_key] will send generated output
/// keys to the broker configured here
pub broker_peer: Option<BrokerPeer>,
/// This is the network address configured for a particular peer at program start.
///
/// I.e. this is the address the rosenpass program will send [crate::msgs::InitHello]
/// packets to, trying to exchange a key.
///
/// Note that the remote peer may connect with another address. See [Self::current_endpoint].
pub initial_endpoint: Option<Endpoint>,
/// The network address currently used for a particular peer.
///
/// This is not necessarily the address that was configured at program start (see [Self::initial_endpoint]),
/// because the remote peer can initiate handshakes from an arbitrary network address.
///
/// If another peer successfully connects to this one from any address, then this field will
/// be updated to reflect which address this was.
pub current_endpoint: Option<Endpoint>,
}
impl AppPeer {
/// Retrieve the [Endpoint] associated with this peer.
///
/// I.e. the [Self::current_endpoint] if set and [Self::initial_endpoint] otherwise.
///
/// # Examples
///
/// ```
/// use rosenpass::app_server::{Endpoint, AppPeer};
/// use rosenpass_util::functional::run;
///
/// let mut peer = AppPeer {
/// outfile: None,
/// broker_peer: None,
/// initial_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:0".to_string())?),
/// current_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:1".to_string())?),
/// };
///
/// fn same(a: Option<&Endpoint>, b: Option<&Endpoint>) -> bool {
/// if a.is_none() && b.is_none() {
/// return true;
/// }
///
/// run(|| Some(std::ptr::eq(a?, b?)) )
/// .unwrap_or(a.is_some() == b.is_some())
/// }
///
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
///
/// let mut tmp = None;
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
///
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
/// std::mem::swap(&mut tmp, &mut peer.current_endpoint);
/// assert!(same(peer.endpoint(), peer.initial_endpoint.as_ref()));
///
/// peer.initial_endpoint = None;
/// peer.current_endpoint = None;
/// assert!(peer.endpoint().is_none());
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn endpoint(&self) -> Option<&Endpoint> {
self.current_endpoint
.as_ref()
@@ -263,8 +126,6 @@ impl AppPeer {
}
}
/// No longer in use since we have the broker system (see [BrokerPeer])
/// TODO: Remove
#[derive(Default, Debug)]
pub struct WireguardOut {
// impl KeyOutput
@@ -273,20 +134,12 @@ pub struct WireguardOut {
pub extra_params: Vec<String>,
}
/// Used to indicate whether the rosenpass server is in normal operating
/// conditions or under load (i.e. a DOS attack is happening)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DoSOperation {
UnderLoad,
Normal,
}
/// Integration test helpers for AppServer
///
/// TODO: Remove; this is no way to write integration tests
///
/// # Examples
///
/// See [AppServer]
#[derive(Debug, Builder)]
#[builder(pattern = "owned")]
pub struct AppServerTest {
@@ -298,100 +151,43 @@ pub struct AppServerTest {
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
}
/// This represents a some source of IO operations in the context of the Rosenpass server
///
/// I.e. this identifies some structure that could be marked as "ready for IO" by [mio]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum AppServerIoSource {
/// IO source refers to a socket in [AppServer::sockets]
Socket(usize),
/// IO source refers to a PSK broker in [AppServer::brokers]
PskBroker(Public<BROKER_ID_BYTES>),
/// IO source refers to some IO sources used in the API;
/// see [AppServer::api_manager]
#[cfg(feature = "experiment_api")]
MioManager(crate::api::mio::MioManagerIoSource),
}
/// Number of epoll(7) events Rosenpass can receive at a time
const EVENT_CAPACITY: usize = 20;
/// This holds pretty much all of the state of the Rosenpass application
/// including the cryptographic state in [Self::crypto_site]
/// Holds the state of the application, namely the external IO
///
/// Responsible for file IO, network IO, etc…
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/app_server_example.rs")]
#[doc = "```"]
/// Responsible for file IO, network IO
// TODO add user control via unix domain socket and stdin/stdout
#[derive(Debug)]
pub struct AppServer {
/// Contains the actual cryptographic implementation.
///
/// Because the API supports initializing the server with a keypair
/// and CryptoServer needs to be initialized with a keypair, the struct
/// struct is wrapped in a ConstructionSite
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
/// The UDP sockets used to send and receive protocol messages
pub sockets: Vec<mio::net::UdpSocket>,
/// Buffer for [mio] (epoll(7), async IO handling) IO events
pub events: mio::Events,
/// Supplemental buffer for [mio] events. See the inline documentation of [AppServer::try_recv]
/// for details.
pub short_poll_queue: VecDeque<mio::event::Event>,
/// We have two different polling modes; long polling (legacy) and short polling (new and
/// somewhat experimental). See [AppServer::try_recv] for details
pub performed_long_poll: bool,
/// Events produced by [mio] refer to IO sources by a numeric token assigned to them
/// (see [MioTokenDispenser]). This index associateds mio token with the specific IO
/// source stored in this object
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
/// Asynchronous IO source
pub mio_poll: mio::Poll,
/// MIO associates IO sources with numeric tokens. This struct takes care of generating these
/// tokens
pub mio_token_dispenser: MioTokenDispenser,
/// Helpers handling communication with WireGuard; these take a generated key and forward it to
/// WireGuard
pub brokers: BrokerStore,
/// This is our view of the peers; generally every peer in here is associated with one peer in
/// CryptoServer
pub peers: Vec<AppPeer>,
/// If set to [Verbosity::Verbose], then some extra information will be printed
/// at the info log level
pub verbosity: Verbosity,
/// Used by [AppServer::try_recv] to ensure that all packages have been read
/// from the UDP sockets
pub all_sockets_drained: bool,
/// Whether network message handling determined that a Denial of Service attack is happening
pub under_load: DoSOperation,
/// State kept by the [AppServer::try_recv] for polling
pub blocking_polls_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub non_blocking_polls_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub unpolled_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub last_update_time: Instant,
/// Used by integration tests to force [Self] into DoS condition
/// and to terminate the AppServer after the test is complete
pub test_helpers: Option<AppServerTest>,
/// Helper for integration tests running rosenpass as a subprocess
/// to terminate properly upon receiving an appropriate system signal.
///
/// This is primarily needed for coverage testing, since llvm-cov does not
/// write coverage reports to disk when a process is stopped by the default
/// signal handler.
///
/// See <https://github.com/rosenpass/rosenpass/issues/385>
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
pub term_signal: terminate::TerminateRequested,
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events,
pub short_poll_queue: VecDeque<mio::event::Event>,
pub performed_long_poll: bool,
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
pub mio_poll: mio::Poll,
pub mio_token_dispenser: MioTokenDispenser,
pub brokers: BrokerStore,
pub peers: Vec<AppPeer>,
pub verbosity: Verbosity,
pub all_sockets_drained: bool,
pub under_load: DoSOperation,
pub blocking_polls_count: usize,
pub non_blocking_polls_count: usize,
pub unpolled_count: usize,
pub last_update_time: Instant,
pub test_helpers: Option<AppServerTest>,
#[cfg(feature = "experiment_api")]
/// The Rosenpass unix socket API handler; this is an experimental
/// feature that can be used to embed Rosenpass in external applications
/// via communication by unix socket
pub api_manager: crate::api::mio::MioManager,
}
@@ -405,19 +201,14 @@ pub struct AppServer {
pub struct SocketPtr(pub usize);
impl SocketPtr {
/// Retrieve the concrete udp socket associated with the pointer
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
&srv.sockets[self.0]
}
/// Retrieve the concrete udp socket associated with the pointer, mutably
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
&mut srv.sockets[self.0]
}
/// Send a UDP packet to another address.
///
/// Merely forwards to [mio::net::UdpSocket::send_to]
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
self.get(srv).send_to(buf, addr)?;
Ok(())
@@ -425,41 +216,28 @@ impl SocketPtr {
}
/// Index based pointer to a Peer
///
/// This allows retrieving both the io-oriented and the cryptographic information
/// about a peer.
#[derive(Debug, Copy, Clone)]
pub struct AppPeerPtr(pub usize);
impl AppPeerPtr {
/// Takes an pointer from the cryptography subsystem
/// in [AppServer::crypto_site] and derives the associated AppPeerPtr
/// in [AppServer]
/// Takes an index based handle and returns the actual peer
pub fn lift(p: PeerPtr) -> Self {
Self(p.0)
}
/// Turns this pointer into a cryptographic peer pointer for [CryptoServer]
/// in [AppServer::crypto_site]
/// Returns an index based handle to one Peer
pub fn lower(&self) -> PeerPtr {
PeerPtr(self.0)
}
/// Retrieve the [AppPeer] pointed to by [Self]
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
&srv.peers[self.0]
}
/// Retrieve the [AppPeer] pointed to by [Self], mutably
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
&mut srv.peers[self.0]
}
/// Use the associated WireGuard PSK broker via [BrokerStorePtr]
/// to upload a new PSK.
///
/// If no PSK broker is set and [AppPeer::outfile] is none, then
/// this prints a warning
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
let config = broker.peer_cfg.create_config(psk);
@@ -472,34 +250,17 @@ impl AppPeerPtr {
}
}
/// The result of [AppServer::poll].
///
/// Instructs [AppServer::event_loop_without_error_handling] on how to proceed.
#[derive(Debug)]
pub enum AppPollResult {
/// Erase the key for a given peer. Corresponds to [crate::protocol::PollResult::DeleteKey]
DeleteKey(AppPeerPtr),
/// Send an initiation to the given peer. Corresponds to [crate::protocol::PollResult::SendInitiation]
SendInitiation(AppPeerPtr),
/// Send a retransmission to the given peer. Corresponds to
/// [crate::protocol::PollResult::SendRetransmission]
SendRetransmission(AppPeerPtr),
/// Received a network message.
///
/// This is the only case without a correspondence in [crate::protocol::PollResult]
ReceivedMessage(usize, Endpoint),
}
/// The reason why we are outputting a key
#[derive(Debug)]
pub enum KeyOutputReason {
/// The reason is that a new key for the given peer was successfully exchanged
Exchanged,
/// The reason is, that no key could be exchanged with the peer before the output
/// key lifetime was reached; a [AppPollResult::DeleteKey] event was issued.
///
/// The key we output in this case is chosen randomly and serves to securely
/// erase whatever key is currently stored.
Stale,
}
@@ -538,21 +299,14 @@ impl std::fmt::Display for Endpoint {
}
}
/// A network address bound to a particular socket.
///
/// We need the information which socket is used because different listen sockets
/// might be on different networks.
#[derive(Debug)]
pub struct SocketBoundEndpoint {
/// The socket the address can be reached under; this is generally
/// determined when we actually receive an RespHello message
socket: SocketPtr,
/// The network address
/// Just the address
addr: SocketAddr,
/// Byte representation of this socket bound network address.
/// Generated through [SocketBoundEndpoint::to_bytes].
///
/// Read through [HostIdentification::encode]
/// identifier
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
}
@@ -563,22 +317,16 @@ impl std::fmt::Display for SocketBoundEndpoint {
}
impl SocketBoundEndpoint {
/// Length in bytes of the serialized socket index
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
/// Length in bytes of the serialized ipv6 address
const IPV6_SIZE: usize = 16;
/// Length in bytes of the serialized port
const PORT_SIZE: usize = 2;
/// Length in bytes of the serialized ipv6 address scope (see [SocketAddrV6::scope_id])
const SCOPE_ID_SIZE: usize = 4;
/// Length in size of
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
+ SocketBoundEndpoint::IPV6_SIZE
+ SocketBoundEndpoint::PORT_SIZE
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
/// Produce a new [Self]
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
let bytes = Self::to_bytes(&socket, &addr);
Self {
@@ -588,7 +336,6 @@ impl SocketBoundEndpoint {
}
}
/// Computes [HostIdentification::encode] for [Self]. Value cached in [Self::bytes].
fn to_bytes(
socket: &SocketPtr,
addr: &SocketAddr,
@@ -623,18 +370,12 @@ impl HostIdentification for SocketBoundEndpoint {
}
impl Endpoint {
/// Given a list of potential network addresses, start peer discovery.
///
/// Will send initiations to different addresses given here on each [crate::msgs::InitHello]
/// retransmission during the peer discovery phase.
/// Start discovery from some addresses
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
}
/// Given a hostname, start peer discovery.
///
/// Will send initiations to different addresses assigned to the host name
/// on each [crate::msgs::InitHello] retransmission during the peer discovery phase.
/// Start endpoint discovery from a hostname
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
Ok(Endpoint::Discovery(host))
@@ -665,8 +406,6 @@ impl Endpoint {
Some(Self::discovery_from_addresses(addrs))
}
/// Send a message to the address referenced by this endpoint or to one of
/// the endpoints if we are in the peer discovery phase for this endpoint
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
use Endpoint::*;
match self {
@@ -675,9 +414,6 @@ impl Endpoint {
}
}
/// List of addresses this endpoint may be associated with.
///
/// During peer discovery, this can be multiple addresses.
fn addresses(&self) -> &[SocketAddr] {
use Endpoint::*;
match self {
@@ -715,14 +451,7 @@ impl Endpoint {
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
#[derive(Debug)]
pub struct HostPathDiscoveryEndpoint {
/// Round robin index the next [Self::send_scouting] call should send packets to
///
/// (address offset, socket offset)
///
/// Including the socket here accounts for the fact that some network addresses may be
/// reachable only through particular UDP sockets
scouting_state: Cell<(usize, usize)>,
/// List of addresses fir oeer discovery
scouting_state: Cell<(usize, usize)>, // addr_off, sock_off
addresses: Vec<SocketAddr>,
}
@@ -733,7 +462,6 @@ impl std::fmt::Display for HostPathDiscoveryEndpoint {
}
impl HostPathDiscoveryEndpoint {
/// Initiate a peer discovery process through a list of potential addresses
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
let scouting_state = Cell::new((0, 0));
Self {
@@ -742,7 +470,7 @@ impl HostPathDiscoveryEndpoint {
}
}
/// Initiate a peer discovery process through hostname lookup
/// Lookup a hostname
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
Ok(Self {
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
@@ -750,14 +478,10 @@ impl HostPathDiscoveryEndpoint {
})
}
/// List of address candidates for the peer
pub fn addresses(&self) -> &Vec<SocketAddr> {
&self.addresses
}
/// Calculates and stores the next value for [Self::scouting_state]
/// given the address and socket we just sent a scouting [crate::msgs::InitHello] message
/// to
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
self.scouting_state.set((
(addr_no + 1) % self.addresses.len(),
@@ -812,11 +536,6 @@ impl HostPathDiscoveryEndpoint {
}
impl AppServer {
/// Construct a new AppServer
///
/// # Examples
///
/// See [Self].
pub fn new(
keypair: Option<(SSk, SPk)>,
addrs: Vec<SocketAddr>,
@@ -941,37 +660,22 @@ impl AppServer {
})
}
/// Access the cryptographic protocol server
///
/// This may return an error if [Self] was initialized without a keypair
/// and no keypair has been supplied since then.
///
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
self.crypto_site
.product_ref()
.context("Cryptography handler not initialized")
}
/// Access the cryptographic protocol server, mutably
///
/// This may return an error if [Self] was initialized without a keypair
/// and no keypair has been supplied since then.
///
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
self.crypto_site
.product_mut()
.context("Cryptography handler not initialized")
}
/// If set to [Verbosity::Verbose], then some extra information will be printed
/// at the info log level
pub fn verbose(&self) -> bool {
matches!(self.verbosity, Verbosity::Verbose)
}
/// Used by [Self::new] to register a new udp listen source
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
let mio_token = self.mio_token_dispenser.dispense();
self.mio_poll
@@ -983,19 +687,16 @@ impl AppServer {
Ok(())
}
/// Used to register a source of IO such as a listen socket with [Self::io_source_index]
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
let prev = self.io_source_index.insert(token, io_source);
assert!(prev.is_none());
}
/// Unregister an IO source registered with [Self::register_io_source]
pub fn unregister_io_source(&mut self, token: mio::Token) {
let value = self.io_source_index.remove(&token);
assert!(value.is_some(), "Removed IO source that does not exist");
}
/// Register a new WireGuard PSK broker
pub fn register_broker(
&mut self,
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
@@ -1018,7 +719,6 @@ impl AppServer {
Ok(BrokerStorePtr(ptr))
}
/// Unregister a WireGuard PSK broker registered with [Self::register_broker]
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
let mut broker = self
.brokers
@@ -1030,11 +730,6 @@ impl AppServer {
Ok(())
}
/// Register a new protocol peer
///
/// # Examples
///
/// See [Self::new].
pub fn add_peer(
&mut self,
psk: Option<SymKey>,
@@ -1063,11 +758,6 @@ impl AppServer {
Ok(AppPeerPtr(pn))
}
/// Main IO handler; this generally does not terminate
///
/// # Examples
///
/// See [Self::new].
pub fn event_loop(&mut self) -> anyhow::Result<()> {
const INIT_SLEEP: f64 = 0.01;
const MAX_FAILURES: i32 = 10;
@@ -1121,9 +811,6 @@ impl AppServer {
}
}
/// IO handler without proactive restarts after errors.
///
/// This is used internally in [Self::event_loop].
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
@@ -1243,8 +930,6 @@ impl AppServer {
}
}
/// Helper for [Self::event_loop_without_error_handling] to handle network messages
/// under DoS condition
fn handle_msg_under_load(
&mut self,
endpoint: &Endpoint,
@@ -1261,8 +946,6 @@ impl AppServer {
}
}
/// Used as a helper by [Self::event_loop_without_error_handling] when
/// a new output key has been echanged
pub fn output_key(
&mut self,
peer: AppPeerPtr,
@@ -1312,10 +995,6 @@ impl AppServer {
Ok(())
}
/// Poll for events from the cryptographic server ([Self::crypto_server()])
/// and for IO events through [Self::poll].
///
/// Used internally in [Self::event_loop_without_error_handling]
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
use crate::protocol::PollResult as C;
use AppPollResult as A;
@@ -1347,9 +1026,7 @@ impl AppServer {
Ok(res)
}
/// Tries to receive a new message from the network sockets.
///
/// Used internally in [Self::poll]
/// Tries to receive a new message
///
/// - might wait for an duration up to `timeout`
/// - returns immediately if an error occurs
@@ -1521,7 +1198,6 @@ impl AppServer {
Ok(None)
}
/// Internal helper for [Self::try_recv]
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
self.mio_poll.poll(&mut self.events, Some(timeout))?;
// Fill the short poll buffer with the acquired events
@@ -1532,7 +1208,6 @@ impl AppServer {
Ok(())
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_mio_token(
&mut self,
buf: &mut [u8],
@@ -1549,7 +1224,6 @@ impl AppServer {
self.try_recv_from_io_source(buf, io_source)
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_io_source(
&mut self,
buf: &mut [u8],
@@ -1580,7 +1254,6 @@ impl AppServer {
}
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_listen_socket(
&mut self,
buf: &mut [u8],
@@ -1616,20 +1289,6 @@ impl AppServer {
}
#[cfg(feature = "experiment_api")]
/// This is a wrapper around a reference to [AppServer] that
/// dishes out references to [AppServer::api_manager] and
/// to [AppServer] itself.
///
/// It really just implements [crate::api::mio::MioManagerContext] which
/// provides the methods operating on [crate::api::mio::MioManager] provided
/// through a trait.
///
/// This is a rather complicated way of providing just a few functions. The entire
/// point of this exercise is to decouple the code in the API from [AppServer] and
/// this file a bit, despite those functions all needing access to [AppServer].
///
/// We want the code to live in its own module instead of expanding and expanding the source
/// file with [AppServer] more and more.
struct MioManagerFocus<'a>(&'a mut AppServer);
#[cfg(feature = "experiment_api")]

View File

@@ -2899,7 +2899,7 @@ impl CryptoServer {
/// This is a lot of code. If you want to read the file outside of the documentation,
/// check out `rosenpass/tests/poll_example.rs" in the repository.
///
#[doc = "```ignore"]
#[doc = "```"]
#[doc = include_str!("../../tests/poll_example.rs")]
#[doc = "```"]
pub fn poll(&mut self) -> Result<PollResult> {

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
// 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;
}

View File

@@ -1,130 +0,0 @@
use std::{
net::SocketAddr,
ops::DerefMut,
path::PathBuf,
str::FromStr,
sync::mpsc,
thread::{self, sleep},
time::Duration,
};
use anyhow::ensure;
use rosenpass::{
app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE},
protocol::{SPk, SSk, SymKey},
};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::Secret;
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
#[test]
fn key_exchange_with_app_server() -> anyhow::Result<()> {
let tmpdir = tempfile::tempdir()?;
let outfile_a = tmpdir.path().join("osk_a");
let outfile_b = tmpdir.path().join("osk_b");
// Set security policy for storing secrets; choose the one that is faster for testing
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
// Introduce the servers to each other
let psk_a = SymKey::random();
let psk_b = psk_a.clone();
let (tx_a, rx_b) = mpsc::sync_channel(1);
let (tx_b, rx_a) = mpsc::sync_channel(1);
let (tx_term_a, rx_term_a) = mpsc::channel();
let (tx_term_b, rx_term_b) = mpsc::channel();
let configs = [
(false, outfile_a.clone(), psk_a, tx_a, rx_a, rx_term_a),
(true, outfile_b.clone(), psk_b, tx_b, rx_b, rx_term_b),
];
for (is_client, osk, psk, tx, rx, rx_term) in configs {
thread::spawn(move || {
run(move || -> anyhow::Result<()> {
let mut srv = TestServer::new(rx_term)?;
tx.send((srv.loopback_port()?, srv.public_key()?.clone()))?;
let (otr_port, otr_pk) = rx.recv()?;
let psk = Some(psk);
let broker_peer = None;
let pk = otr_pk;
let outfile = Some(osk);
let port = otr_port;
let hostname = is_client.then(|| format!("[::1]:{port}"));
srv.app_srv
.add_peer(psk, pk, outfile, broker_peer, hostname)?;
srv.app_srv.event_loop()
})
.unwrap();
});
}
// Busy wait for both keys to be exchanged
let mut successful_exchange = false;
for _ in 0..2000 {
// 40s
sleep(Duration::from_millis(20));
run(|| -> anyhow::Result<()> {
let osk_a = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_a)?;
let osk_b = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_b)?;
successful_exchange = rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret());
Ok(())
})
.discard_result();
if successful_exchange {
break;
}
}
// Tell the parties to terminate
tx_term_a.send(())?;
tx_term_b.send(())?;
assert!(
successful_exchange,
"Test did not complete successfully within the deadline"
);
Ok(())
}
struct TestServer {
app_srv: AppServer,
}
impl TestServer {
fn new(termination_queue: mpsc::Receiver<()>) -> anyhow::Result<Self> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
let keypair = Some((sk, pk));
let addrs = vec![
SocketAddr::from_str("[::1]:0")?, // Localhost, any port. For connecting to the test server.
// ipv4_any_binding(), // any IPv4 interface
// ipv6_any_binding(), // any IPv6 interface
];
let verbosity = rosenpass::config::Verbosity::Verbose;
let test_helpers = Some(AppServerTest {
enable_dos_permanently: false,
termination_handler: Some(termination_queue),
});
let app_srv = AppServer::new(keypair, addrs, verbosity, test_helpers)?;
Self { app_srv }.ok()
}
fn loopback_port(&self) -> anyhow::Result<u16> {
self.app_srv.sockets[0].local_addr()?.port().ok()
}
fn public_key(&self) -> anyhow::Result<&SPk> {
Ok(&self.app_srv.crypto_server()?.spkm)
}
}

View File

@@ -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"),

View File

@@ -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(())

View File

@@ -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()) {

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};
@@ -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,

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;
/// 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);
}
}

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),
/// [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)
}

View File

@@ -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;

View File

@@ -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;

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};
/// 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>,

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 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>,

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 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,

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
///
/// 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
///

View File

@@ -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);

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
/// 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(()),

View File

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

View File

@@ -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;

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::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);
}
}

View File

@@ -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>;
}

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
/// 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,

View File

@@ -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;

View File

@@ -3,7 +3,23 @@ use typenum::int::{NInt, PInt, Z0};
use typenum::marker_traits as markers;
use typenum::uint::{UInt, UTerm};
/// Convenience macro to convert type numbers to constant integers
/// Convenience macro to convert [`typenum`] type numbers to constant integers.
///
/// This macro takes a [`typenum`] type-level integer (like `U5`, `P3`, or `N7`)
/// and converts it into its equivalent constant integer value at compile time.
/// By default, it converts to a suitable unsigned integer type, but you can
/// specify a target type explicitly using `typenum2const!(Type as i32)`,
/// for example.
///
/// # Examples
///
/// ```rust
/// # use typenum::consts::U10;
/// # use rosenpass_util::typenum2const;
///
/// const TEN: u32 = typenum2const!(U10 as u32);
/// assert_eq!(TEN, 10);
/// ```
#[macro_export]
macro_rules! typenum2const {
($val:ty) => {
@@ -14,35 +30,80 @@ macro_rules! typenum2const {
};
}
/// Trait implemented by constant integers to facilitate conversion to constant integers
/// A trait implemented by type-level integers to facilitate their conversion
/// into constant values.
///
/// Types from the [`typenum`] crate (like `U5`, `P3`, or `N7`) can implement
/// `IntoConst` to produce a compile-time constant integer of the specified
/// type. This trait is part of the underlying mechanism used by the
/// [`crate::typenum2const`] macro.
///
/// # Examples
///
/// ```rust
/// use rosenpass_util::typenum2const;
/// use typenum::consts::U42;
/// use rosenpass_util::typenum::IntoConst;
///
/// // Directly using IntoConst:
/// const VALUE: u64 = <U42 as IntoConst<u64>>::VALUE;
/// assert_eq!(VALUE, 42);
///
/// // Or via the macro:
/// const VALUE_MACRO: u64 = typenum2const!(U42 as u64);
/// assert_eq!(VALUE_MACRO, 42);
/// ```
pub trait IntoConst<T> {
/// The constant value after conversion
/// The constant value after conversion.
const VALUE: T;
}
#[allow(dead_code)]
/// Internal struct for applying a negative sign to an unsigned type-level integer during conversion.
///
/// This is part of the implementation detail for signed conversions. It uses
/// [`AssociatedUnsigned`] to determine the underlying unsigned type and negates its value.
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
#[allow(dead_code)]
/// Internal struct for applying a positive sign to an unsigned type-level integer during conversion.
///
/// This is used as part of converting a positive signed type-level integer to its runtime integer
/// value, ensuring that the correct unsigned representation is known via [`AssociatedUnsigned`].
struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
*const T,
*const Param,
);
#[allow(dead_code)]
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
/// Internal struct representing a left-shift operation on a type-level integer.
///
/// Used as part of compile-time computations. `SHIFT` determines how many bits the value will be
/// shifted to the left.
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param);
#[allow(dead_code)]
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
/// Internal struct representing an addition operation between two type-level integers.
///
/// `ConstAdd` is another building block for compile-time arithmetic on type-level integers before
/// their conversion to runtime constants.
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs);
/// Assigns an unsigned type to a signed type
/// Associates an unsigned type with a signed type, enabling conversions between signed and unsigned
/// representations of compile-time integers.
///
/// This trait is used internally to facilitate the conversion of signed [`typenum`] integers by
/// referencing their underlying unsigned representation.
trait AssociatedUnsigned {
/// The associated unsigned type.
type Type;
}
/// Internal macro implementing the [`IntoConst`] trait for a given mapping from a type-level integer
/// to a concrete integer type.
macro_rules! impl_into_const {
( $from:ty as $to:ty := $impl:expr) => {
impl IntoConst<$to> for $from {
@@ -51,6 +112,10 @@ macro_rules! impl_into_const {
};
}
/// Internal macro implementing common `IntoConst` logic for various numeric types.
///
/// It sets up `Z0`, `B0`, `B1`, `UTerm`, and also provides default implementations for
/// `ConstLshift` and `ConstAdd`.
macro_rules! impl_numeric_into_const_common {
($type:ty) => {
impl_into_const! { Z0 as $type := 0 }
@@ -73,6 +138,10 @@ macro_rules! impl_numeric_into_const_common {
};
}
/// Internal macro implementing `IntoConst` for unsigned integer types.
///
/// It sets up conversions for multiple unsigned integer target types and
/// provides the positive sign application implementation.
macro_rules! impl_numeric_into_const_unsigned {
($($to_list:ty),*) => {
$( impl_numeric_into_const_unsigned! { @impl $to_list } )*
@@ -91,6 +160,9 @@ macro_rules! impl_numeric_into_const_unsigned {
};
}
/// Internal macro implementing `IntoConst` for signed integer types.
///
/// It uses their associated unsigned types to handle positive and negative conversions correctly.
macro_rules! impl_numeric_into_const_signed {
($($to_list:ty : $unsigned_list:ty),*) => {
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )*
@@ -110,9 +182,8 @@ macro_rules! impl_numeric_into_const_signed {
impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyNegSign<$type, Param> {
const VALUE : $type =
if Param::VALUE == (1 as $unsigned).rotate_right(1) {
// Handle the negative value without an associated positive value (e.g. -128
// for i8)
< $type >::MIN
// Handling negative values at boundaries, such as i8::MIN
<$type>::MIN
} else {
-(Param::VALUE as $type)
};
@@ -122,10 +193,10 @@ macro_rules! impl_numeric_into_const_signed {
impl_into_const! { B0 as bool := false }
impl_into_const! { B1 as bool := true }
impl_numeric_into_const_unsigned! { usize, u8, u16, u32, u64, u128 }
impl_numeric_into_const_signed! { isize : usize, i8 : u8, i16 : u16, i32 : u32, i64 : u64, i128 : u128 }
// Unsigned type numbers to const integers
impl<Ret, Rest, Bit> IntoConst<Ret> for UInt<Rest, Bit>
where
Rest: IntoConst<Ret>,
@@ -133,26 +204,28 @@ where
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
{
/// Converts an unsigned [`UInt`] typenum into its corresponding constant integer by
/// decomposing it into shifts and additions on its subparts.
const VALUE: Ret = <ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with positive sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a positive signed [`PInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyPosSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with negative sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a negative signed [`NInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}

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