mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-11 15:20:29 -08:00
Compare commits
50 Commits
dev/karo/a
...
docu-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf91c84f0 | ||
|
|
f5b4c17011 | ||
|
|
12506e5f95 | ||
|
|
965600212d | ||
|
|
d807a1bca7 | ||
|
|
4daf97b2ee | ||
|
|
b394e302ab | ||
|
|
198bc2d5f2 | ||
|
|
fc2f535eae | ||
|
|
302e249f08 | ||
|
|
d8fe3eba5f | ||
|
|
61b8b28e86 | ||
|
|
26f77924f8 | ||
|
|
2e0e2cfa0c | ||
|
|
9cc860fdeb | ||
|
|
a537eb3e1b | ||
|
|
ea233bf137 | ||
|
|
db8796ab40 | ||
|
|
ae3fbde0a3 | ||
|
|
51d4dede15 | ||
|
|
4725a2d628 | ||
|
|
a6bac74d48 | ||
|
|
b9a34f4238 | ||
|
|
46e855b266 | ||
|
|
c0b91fd729 | ||
|
|
97dff8453d | ||
|
|
a3d4686104 | ||
|
|
cee0678817 | ||
|
|
a996f194c7 | ||
|
|
447be89414 | ||
|
|
ef4f550abc | ||
|
|
4737cd2b2a | ||
|
|
9336794e4d | ||
|
|
096bac6ee5 | ||
|
|
161826979a | ||
|
|
c435b772d2 | ||
|
|
8805ef7c38 | ||
|
|
cca02dc8d1 | ||
|
|
d4350195eb | ||
|
|
1c5e4ecf95 | ||
|
|
b15947b815 | ||
|
|
cacbf8535c | ||
|
|
f6d9da4a18 | ||
|
|
68f73e264d | ||
|
|
d5f68dcbd2 | ||
|
|
96581ed118 | ||
|
|
553b058759 | ||
|
|
85286c146f | ||
|
|
0f58b36c5b | ||
|
|
5251721bcf |
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -403,9 +403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.38"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
|
||||
checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
|
||||
dependencies = [
|
||||
"clap 4.5.23",
|
||||
]
|
||||
@@ -815,12 +815,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1204,9 +1204,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.167"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libcrux"
|
||||
@@ -1865,6 +1865,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rosenpass-cipher-traits"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rosenpass-oqs",
|
||||
"rosenpass-secret-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-ciphers"
|
||||
@@ -2054,15 +2059,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.41"
|
||||
version = "0.38.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
|
||||
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2109,18 +2114,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -49,8 +49,8 @@ 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"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
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"] }
|
||||
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||
@@ -90,4 +90,4 @@ procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||
#Broker dependencies (might need cleanup or changes)
|
||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||
command-fds = "0.2.3"
|
||||
rustix = { version = "0.38.41", features = ["net", "fs", "process"] }
|
||||
rustix = { version = "0.38.42", features = ["net", "fs", "process"] }
|
||||
|
||||
@@ -10,3 +10,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
rosenpass-oqs = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
anyhow = {workspace = true}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Rosenpass internal library providing traits for cryptographic primitives.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
This is an internal library; no guarantee is made about its API at this point in time.
|
||||
|
||||
@@ -5,10 +5,128 @@
|
||||
//!
|
||||
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
|
||||
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
|
||||
//!
|
||||
//! encapsulation.
|
||||
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
||||
//!
|
||||
//! The [Kem] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided:
|
||||
//! [Kyber512](../../rosenpass_oqs/kyber_512/enum.Kyber512.html) and
|
||||
//! [ClassicMceliece460896](../../rosenpass_oqs/classic_mceliece_460896/enum.ClassicMceliece460896.html).
|
||||
//!
|
||||
//! An example where Alice generates a keypair and gives her public key to Bob, for Bob to
|
||||
//! encapsulate a symmetric key and Alice to decapsulate it would look as follows.
|
||||
//! In the example, we are using Kyber512, but any KEM that correctly implements the [Kem]
|
||||
//! trait could be used as well.
|
||||
//!```rust
|
||||
//! use rosenpass_cipher_traits::Kem;
|
||||
//! use rosenpass_oqs::Kyber512;
|
||||
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
//!
|
||||
//! type MyKem = Kyber512;
|
||||
//! secret_policy_use_only_malloc_secrets();
|
||||
//! let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||
//! let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||
//! MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||
//!
|
||||
//! let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||
//! MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||
//!
|
||||
//! let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||
//!
|
||||
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||
//! # Ok::<(), anyhow::Error>(())
|
||||
//!```
|
||||
//!
|
||||
//! Implementing the [Kem]-trait for a KEM is easy. Mostly, you must format the KEM's
|
||||
//! keys, and ciphertext as `u8` slices. Below, we provide an example for how the trait can
|
||||
//! be implemented using a **HORRIBLY INSECURE** DummyKem that only uses static values for keys
|
||||
//! and ciphertexts as an example.
|
||||
//!```rust
|
||||
//!# use rosenpass_cipher_traits::Kem;
|
||||
//!
|
||||
//! struct DummyKem {}
|
||||
//! impl Kem for DummyKem {
|
||||
//!
|
||||
//! // For this DummyKem, using String for errors is sufficient.
|
||||
//! type Error = String;
|
||||
//!
|
||||
//! // For this DummyKem, we will use a single `u8` for everything
|
||||
//! const SK_LEN: usize = 1;
|
||||
//! const PK_LEN: usize = 1;
|
||||
//! const CT_LEN: usize = 1;
|
||||
//! const SHK_LEN: usize = 1;
|
||||
//!
|
||||
//! fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error> {
|
||||
//! if sk.len() != Self::SK_LEN {
|
||||
//! return Err("sk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if pk.len() != Self::PK_LEN {
|
||||
//! return Err("pk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! sk[0] = 42;
|
||||
//! pk[0] = 21;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error> {
|
||||
//! if pk.len() != Self::PK_LEN {
|
||||
//! return Err("pk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if ct.len() != Self::CT_LEN {
|
||||
//! return Err("ct does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if shk.len() != Self::SHK_LEN {
|
||||
//! return Err("shk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if pk[0] != 21 {
|
||||
//! return Err("Invalid public key!".to_string());
|
||||
//! }
|
||||
//! ct[0] = 7;
|
||||
//! shk[0] = 17;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error> {
|
||||
//! if sk.len() != Self::SK_LEN {
|
||||
//! return Err("sk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if ct.len() != Self::CT_LEN {
|
||||
//! return Err("ct does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if shk.len() != Self::SHK_LEN {
|
||||
//! return Err("shk does not have the correct length!".to_string());
|
||||
//! }
|
||||
//! if sk[0] != 42 {
|
||||
//! return Err("Invalid public key!".to_string());
|
||||
//! }
|
||||
//! if ct[0] != 7 {
|
||||
//! return Err("Invalid ciphertext!".to_string());
|
||||
//! }
|
||||
//! shk[0] = 17;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
//! #
|
||||
//! # type MyKem = DummyKem;
|
||||
//! # secret_policy_use_only_malloc_secrets();
|
||||
//! # let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
|
||||
//! # let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
|
||||
//! # MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
|
||||
//!
|
||||
//! # let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! # let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
|
||||
//! # MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
|
||||
//! #
|
||||
//! # let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
|
||||
//! # MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
|
||||
//! #
|
||||
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
|
||||
//! #
|
||||
//! # Ok::<(), String>(())
|
||||
//!```
|
||||
//!
|
||||
|
||||
/// Key Encapsulation Mechanism
|
||||
///
|
||||
|
||||
@@ -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>(())
|
||||
///```
|
||||
///
|
||||
|
||||
|
||||
@@ -41,10 +41,11 @@ pub mod xaead {
|
||||
pub mod hash_domain;
|
||||
|
||||
/// This crate includes two key encapsulation mechanisms.
|
||||
/// Namely ClassicMceliece460896 (as [StaticKem]) and Kyber512 (as [EphemeralKem]).
|
||||
/// Namely ClassicMceliece460896 (also referred to as `StaticKem` sometimes) and
|
||||
/// Kyber512 (also referred to as `EphemeralKem` sometimes).
|
||||
///
|
||||
/// See [rosenpass_oqs::ClassicMceliece460896](rosenpass_oqs::ClassicMceliece460896)
|
||||
/// and [rosenpass_oqs::Kyber512](rosenpass_oqs::Kyber512) for more details on the specific KEMS.
|
||||
/// See [rosenpass_oqs::ClassicMceliece460896]
|
||||
/// and [rosenpass_oqs::Kyber512] for more details on the specific KEMS.
|
||||
///
|
||||
pub mod kem {
|
||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||
|
||||
@@ -10,7 +10,7 @@ use rosenpass_to::{ops::copy_slice, with_destination, To};
|
||||
use rosenpass_util::typenum2const;
|
||||
|
||||
/// Specify that the used implementation of BLAKE2b is the MAC version of BLAKE2b
|
||||
/// with output and key length of 32 bytes (see [Blake2bMac<U32>]).
|
||||
/// with output and key length of 32 bytes (see [Blake2bMac]).
|
||||
type Impl = Blake2bMac<U32>;
|
||||
|
||||
type KeyLen = <Impl as KeySizeUser>::KeySize;
|
||||
@@ -21,17 +21,17 @@ const KEY_LEN: usize = typenum2const! { KeyLen };
|
||||
/// The output length for BLAKE2b supported by this API. Currently 32 Bytes.
|
||||
const OUT_LEN: usize = typenum2const! { OutLen };
|
||||
|
||||
/// Minimal key length supported by this API (identical to [KEY_LEN])
|
||||
/// Minimal key length supported by this API.
|
||||
pub const KEY_MIN: usize = KEY_LEN;
|
||||
/// maximal key length supported by this API (identical to [KEY_LEN])
|
||||
/// maximal key length supported by this API.
|
||||
pub const KEY_MAX: usize = KEY_LEN;
|
||||
/// minimal output length supported by this API (identical [OUT_LEN])
|
||||
/// minimal output length supported by this API.
|
||||
pub const OUT_MIN: usize = OUT_LEN;
|
||||
/// maximal output length supported by this API (identical [OUT_LEN])
|
||||
/// maximal output length supported by this API.
|
||||
pub const OUT_MAX: usize = OUT_LEN;
|
||||
|
||||
/// Hashes the given `data` with the [Blake2bMac<U32>] hash function under the given `key`.
|
||||
/// The [KEY_LEN] and [OUT_LEN] are both set to 32 bytes (or 256 bits).
|
||||
/// Hashes the given `data` with the [Blake2bMac] hash function under the given `key`.
|
||||
/// The both the length of the output the length of the key 32 bytes (or 256 bits).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// This module provides the following cryptographic schemes:
|
||||
/// - [blake2b]: The blake2b hash function
|
||||
/// - [chacha20poly1305_ietf]: The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305) (only used when the feature `experiment_libcrux` is disabled.
|
||||
/// - [chacha20poly1305_ietf_libcrux]: The Chacha20Poly1305 AEAD as implemented in [libcrux](https://github.com/cryspen/libcrux) (only used when the feature `experiment_libcrux` is enabled.
|
||||
/// - [chacha20poly1305_ietf]: The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305) (only used when the feature `experiment_libcrux` is disabled).
|
||||
/// - [chacha20poly1305_ietf_libcrux]: The Chacha20Poly1305 AEAD as implemented in [libcrux](https://github.com/cryspen/libcrux) (only used when the feature `experiment_libcrux` is enabled).
|
||||
/// - [incorrect_hmac_blake2b]: An (incorrect) hmac based on [blake2b].
|
||||
/// - [xchacha20poly1305_ietf] The Chacha20Poly1305 AEAD as implemented in [RustCrypto](https://crates.io/crates/chacha20poly1305)
|
||||
pub mod blake2b;
|
||||
|
||||
@@ -20,3 +20,6 @@ memsec = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }
|
||||
|
||||
@@ -2,14 +2,29 @@
|
||||
|
||||
use core::ptr;
|
||||
|
||||
/// Little endian memcmp version of quinier/memsec
|
||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
||||
/// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30)
|
||||
///
|
||||
/// # Panic & Safety
|
||||
///
|
||||
/// Both input arrays must be at least of the indicated length.
|
||||
///
|
||||
/// See [std::ptr::read_volatile] on safety.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let a = [1, 2, 3, 4];
|
||||
/// let b = [1, 2, 3, 4];
|
||||
/// let c = [1, 2, 2, 5];
|
||||
/// let d = [1, 2, 2, 4];
|
||||
///
|
||||
/// unsafe {
|
||||
/// use rosenpass_constant_time::memcmp_le;
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
/// assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(never)]
|
||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
let mut res = 0;
|
||||
@@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compare::memcmp_le;
|
||||
|
||||
#[test]
|
||||
fn memcmp_le_test() {
|
||||
let a = [1, 2, 3, 4];
|
||||
let b = [1, 2, 3, 4];
|
||||
let c = [1, 2, 2, 5];
|
||||
let d = [1, 2, 2, 4];
|
||||
|
||||
unsafe {
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
|
||||
assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
|
||||
assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
|
||||
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,16 @@ use core::hint::black_box;
|
||||
/// and increment that integer.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - The number of carry operations that occur may affect timing slightly
|
||||
/// - Memory access patterns are sequential and predictable
|
||||
///
|
||||
/// The carry operation timing variation is mitigated through the use of black_box,
|
||||
/// but the linear scaling with input size is inherent to the operation.
|
||||
/// These timing characteristics are generally considered acceptable for most
|
||||
/// cryptographic counter implementations.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
@@ -7,6 +7,32 @@
|
||||
//! ## TODO
|
||||
//! Figure out methodology to ensure that code is actually constant time, see
|
||||
//! <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rosenpass_constant_time::{memcmp, compare};
|
||||
//!
|
||||
//! let a = [1, 2, 3, 4];
|
||||
//! let b = [1, 2, 3, 4];
|
||||
//! let c = [1, 2, 3, 5];
|
||||
//!
|
||||
//! // Compare for equality
|
||||
//! assert!(memcmp(&a, &b));
|
||||
//! assert!(!memcmp(&a, &c));
|
||||
//!
|
||||
//! // Compare lexicographically
|
||||
//! assert_eq!(compare(&a, &c), -1); // a < c
|
||||
//! assert_eq!(compare(&c, &a), 1); // c > a
|
||||
//! assert_eq!(compare(&a, &b), 0); // a == b
|
||||
//! ```
|
||||
//!
|
||||
//! # Security Notes
|
||||
//!
|
||||
//! While these functions aim to be constant-time, they may leak timing information in some cases:
|
||||
//!
|
||||
//! - Length mismatches between inputs are immediately detectable
|
||||
//! - Execution time scales linearly with input size
|
||||
|
||||
mod compare;
|
||||
mod increment;
|
||||
@@ -14,6 +40,7 @@ mod memcmp;
|
||||
mod xor;
|
||||
|
||||
pub use compare::compare;
|
||||
pub use compare::memcmp_le;
|
||||
pub use increment::increment;
|
||||
pub use memcmp::memcmp;
|
||||
pub use xor::xor;
|
||||
|
||||
@@ -113,9 +113,10 @@ mod tests {
|
||||
// Pearson correlation
|
||||
let correlation = cv / (sd_x * sd_y);
|
||||
println!("correlation: {:.6?}", correlation);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
correlation.abs() < 0.01,
|
||||
"execution time correlates with result"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To};
|
||||
|
||||
/// Xors the source into the destination
|
||||
///
|
||||
/// Performs a constant-time XOR operation between two byte slices
|
||||
///
|
||||
/// Takes a source slice and XORs it with the destination slice in-place using the
|
||||
/// rosenpass_to trait for destination management.
|
||||
///
|
||||
/// # Panics
|
||||
/// If source and destination are of different sizes.
|
||||
///
|
||||
/// # Leaks
|
||||
/// TODO: mention here if this function leaks any information, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
/// This function may leak timing information in the following ways:
|
||||
///
|
||||
/// - The function execution time is linearly proportional to the input length
|
||||
/// - Length mismatches between source and destination are immediately detectable via panic
|
||||
/// - Memory access patterns follow a predictable sequential pattern
|
||||
///
|
||||
/// These leaks are generally considered acceptable in most cryptographic contexts
|
||||
/// as they don't reveal information about the actual content being XORed.
|
||||
///
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
|
||||
@@ -21,8 +21,9 @@ main() {
|
||||
open="1"
|
||||
fi
|
||||
|
||||
exc cargo llvm-cov --all-features --workspace --doctests
|
||||
exc cargo llvm-cov --all-features --workspace --doctests --branch
|
||||
|
||||
exc cp -rv target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/doctestbins
|
||||
exc rm -rf "${OUTPUT_DIR}"
|
||||
exc mkdir -p "${OUTPUT_DIR}"
|
||||
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
||||
|
||||
@@ -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)'] }
|
||||
|
||||
@@ -167,6 +167,8 @@ const EVENT_CAPACITY: usize = 20;
|
||||
// TODO add user control via unix domain socket and stdin/stdout
|
||||
#[derive(Debug)]
|
||||
pub struct AppServer {
|
||||
#[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,
|
||||
@@ -633,6 +635,8 @@ impl AppServer {
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
term_signal: terminate::TerminateRequested::new()?,
|
||||
crypto_site,
|
||||
peers: Vec::new(),
|
||||
verbosity,
|
||||
@@ -754,18 +758,35 @@ impl AppServer {
|
||||
Ok(AppPeerPtr(pn))
|
||||
}
|
||||
|
||||
pub fn listen_loop(&mut self) -> anyhow::Result<()> {
|
||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||
const INIT_SLEEP: f64 = 0.01;
|
||||
const MAX_FAILURES: i32 = 10;
|
||||
let mut failure_cnt = 0;
|
||||
|
||||
loop {
|
||||
let msgs_processed = 0usize;
|
||||
let err = match self.event_loop() {
|
||||
let err = match self.event_loop_without_error_handling() {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
{
|
||||
let terminated_by_signal = err
|
||||
.downcast_ref::<std::io::Error>()
|
||||
.filter(|e| e.kind() == std::io::ErrorKind::Interrupted)
|
||||
.filter(|_| self.term_signal.value())
|
||||
.is_some();
|
||||
if terminated_by_signal {
|
||||
log::warn!(
|
||||
"\
|
||||
Terminated by signal; this signal handler is correct during coverage testing \
|
||||
but should be otherwise disabled"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// This should not happen…
|
||||
failure_cnt = if msgs_processed > 0 {
|
||||
0
|
||||
@@ -790,7 +811,7 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
|
||||
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
||||
|
||||
/// if socket address for peer is known, call closure
|
||||
@@ -1288,3 +1309,48 @@ impl crate::api::mio::MioManagerContext for MioManagerFocus<'_> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// These signal handlers are used exclusively used during coverage testing
|
||||
/// to ensure that the llvm-cov can produce reports during integration tests
|
||||
/// with multiple processes where subprocesses are terminated via kill(2).
|
||||
///
|
||||
/// llvm-cov does not support producing coverage reports when the process exits
|
||||
/// through a signal, so this is necessary.
|
||||
///
|
||||
/// The functionality of exiting gracefully upon reception of a terminating signal
|
||||
/// is desired for the production variant of Rosenpass, but we should make sure
|
||||
/// to use a higher quality implementation; in particular, we should use signalfd(2).
|
||||
///
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
mod terminate {
|
||||
use signal_hook::flag::register as sig_register;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
/// Automatically register a signal handler for common termination signals;
|
||||
/// whether one of these signals was issued can be polled using [Self::value].
|
||||
///
|
||||
/// The signal handler is not removed when this struct goes out of scope.
|
||||
#[derive(Debug)]
|
||||
pub struct TerminateRequested {
|
||||
value: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TerminateRequested {
|
||||
/// Register signal handlers watching for common termination signals
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let value = Arc::new(AtomicBool::new(false));
|
||||
for sig in signal_hook::consts::TERM_SIGNALS.iter().copied() {
|
||||
sig_register(sig, Arc::clone(&value))?;
|
||||
}
|
||||
Ok(Self { value })
|
||||
}
|
||||
|
||||
/// Check whether a termination signal has been set
|
||||
pub fn value(&self) -> bool {
|
||||
self.value.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use clap::Parser;
|
||||
use clap_mangen::roff::{roman, Roff};
|
||||
use log::error;
|
||||
use rosenpass::cli::CliArgs;
|
||||
use rosenpass_util::functional::run;
|
||||
use std::process::exit;
|
||||
|
||||
/// Printing custom man sections when generating the man page
|
||||
@@ -78,40 +77,13 @@ pub fn main() {
|
||||
// error!("error dummy");
|
||||
}
|
||||
|
||||
let res = run(|| {
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
let term_signal = terminate::TerminateRequested::new()?;
|
||||
|
||||
let broker_interface = args.get_broker_interface();
|
||||
let err = match args.run(broker_interface, None) {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
// This is very very hacky and just used for coverage measurement
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
{
|
||||
let terminated_by_signal = err
|
||||
.downcast_ref::<std::io::Error>()
|
||||
.filter(|e| e.kind() == std::io::ErrorKind::Interrupted)
|
||||
.filter(|_| term_signal.value())
|
||||
.is_some();
|
||||
if terminated_by_signal {
|
||||
log::warn!(
|
||||
"\
|
||||
Terminated by signal; this signal handler is correct during coverage testing \
|
||||
but should be otherwise disabled"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let broker_interface = args.get_broker_interface();
|
||||
match args.run(broker_interface, None) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Err(err)
|
||||
});
|
||||
|
||||
if let Err(e) = res {
|
||||
error!("{e:?}");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,47 +110,3 @@ Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
||||
/// Custom main page section: Bugs.
|
||||
static BUGS_MAN: &str = r"
|
||||
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
||||
|
||||
/// These signal handlers are used exclusively used during coverage testing
|
||||
/// to ensure that the llvm-cov can produce reports during integration tests
|
||||
/// with multiple processes where subprocesses are terminated via kill(2).
|
||||
///
|
||||
/// llvm-cov does not support producing coverage reports when the process exits
|
||||
/// through a signal, so this is necessary.
|
||||
///
|
||||
/// The functionality of exiting gracefully upon reception of a terminating signal
|
||||
/// is desired for the production variant of Rosenpass, but we should make sure
|
||||
/// to use a higher quality implementation; in particular, we should use signalfd(2).
|
||||
///
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
mod terminate {
|
||||
use signal_hook::flag::register as sig_register;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
/// Automatically register a signal handler for common termination signals;
|
||||
/// whether one of these signals was issued can be polled using [Self::value].
|
||||
///
|
||||
/// The signal handler is not removed when this struct goes out of scope.
|
||||
pub struct TerminateRequested {
|
||||
value: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TerminateRequested {
|
||||
/// Register signal handlers watching for common termination signals
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let value = Arc::new(AtomicBool::new(false));
|
||||
for sig in signal_hook::consts::TERM_SIGNALS.iter().copied() {
|
||||
sig_register(sig, Arc::clone(&value))?;
|
||||
}
|
||||
Ok(Self { value })
|
||||
}
|
||||
|
||||
/// Check whether a termination signal has been set
|
||||
pub fn value(&self) -> bool {
|
||||
self.value.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
//! # Example Handshake
|
||||
//!
|
||||
//! This example illustrates a minimal setup for a key-exchange between two
|
||||
//! [CryptoServer].
|
||||
//! [CryptoServer]s; this is what we use for some testing purposes but it is not
|
||||
//! what should be used in a real world application, as timing-based events
|
||||
//! are handled by [CryptoServer::poll].
|
||||
//!
|
||||
//! See [CryptoServer::poll] on how to use crypto server in polling mode for production usage.
|
||||
//!
|
||||
//! ```
|
||||
//! use std::ops::DerefMut;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,15 @@ impl Drop for KillChild {
|
||||
fn drop(&mut self) {
|
||||
use rustix::process::{kill_process, Pid, Signal::Term};
|
||||
let pid = Pid::from_child(&self.0);
|
||||
rustix::process::kill_process(pid, Term).discard_result();
|
||||
self.0.wait().discard_result();
|
||||
// We seriously need to start handling signals with signalfd, our current signal handling
|
||||
// system is a bit broken; there is probably a few functions that just restart on EINTR
|
||||
// so the signal is absorbed
|
||||
loop {
|
||||
kill_process(pid, Term).discard_result();
|
||||
if self.0.try_wait().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,9 @@ fn check_example_config() {
|
||||
.output()
|
||||
.expect("EXAMPLE_CONFIG not valid");
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(stderr, "");
|
||||
|
||||
fs::copy(
|
||||
tmp_dir.path().join("rp-public-key"),
|
||||
tmp_dir.path().join("rp-peer-public-key"),
|
||||
|
||||
642
rosenpass/tests/poll_example.rs
Normal file
642
rosenpass/tests/poll_example.rs
Normal file
@@ -0,0 +1,642 @@
|
||||
/// This file contains a correct simulation of a two-party key exchange using Poll
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
collections::VecDeque,
|
||||
ops::DerefMut,
|
||||
};
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_util::result::OkExt;
|
||||
|
||||
use rosenpass::protocol::{
|
||||
testutils::time_travel_forward, CryptoServer, HostIdentification, MsgBuf, PeerPtr, PollResult,
|
||||
SPk, SSk, SymKey, Timing, UNENDING,
|
||||
};
|
||||
|
||||
// TODO: Most of the utility functions in here should probably be moved to
|
||||
// rosenpass::protocol::testutils;
|
||||
|
||||
#[test]
|
||||
fn test_successful_exchange_with_poll() -> anyhow::Result<()> {
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let mut sim = RosenpassSimulator::new()?;
|
||||
sim.poll_loop(150)?; // Poll 75 times
|
||||
let transcript = sim.transcript;
|
||||
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions[0].0 < 20.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[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:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_successful_exchange_under_packet_loss() -> anyhow::Result<()> {
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
// Create the simulator
|
||||
let mut sim = RosenpassSimulator::new()?;
|
||||
|
||||
// Make sure the servers are set to under load condition
|
||||
sim.srv_a.under_load = true;
|
||||
sim.srv_b.under_load = false; // See Issue #539 -- https://github.com/rosenpass/rosenpass/issues/539
|
||||
|
||||
// Perform the key exchanges
|
||||
let mut pkg_counter = 0usize;
|
||||
for _ in 0..300 {
|
||||
let ev = sim.poll()?;
|
||||
if let TranscriptEvent::ServerEvent {
|
||||
source,
|
||||
event: ServerEvent::Transmit(_, _),
|
||||
} = ev
|
||||
{
|
||||
// Drop every fifth package
|
||||
if pkg_counter % 10 == 0 {
|
||||
source.drop_outgoing_packet(&mut sim);
|
||||
}
|
||||
|
||||
pkg_counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let transcript = sim.transcript;
|
||||
let _completions: Vec<_> = transcript
|
||||
.iter()
|
||||
.filter(|elm| matches!(elm, (_, TranscriptEvent::CompletedExchange(_))))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
!_completions.is_empty(),
|
||||
"\
|
||||
Should have performed a successful key exchanged!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions[0].0 < 10.0,
|
||||
"\
|
||||
First key exchange should happen in under twenty seconds!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
_completions.len() >= 3,
|
||||
"\
|
||||
Should have at least two renegotiations!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[cfg(not(coverage))]
|
||||
assert!(
|
||||
(110.0..175.0).contains(&_completions[1].0),
|
||||
"\
|
||||
First renegotiation should happen in between two and three minutes!\n\
|
||||
Transcript: {transcript:?}\n\
|
||||
Completions: {_completions:?}\
|
||||
"
|
||||
);
|
||||
#[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:?}\
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type MessageType = u8;
|
||||
|
||||
/// Lets record the events that are produced by Rosenpass
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
enum TranscriptEvent {
|
||||
Wait(Timing),
|
||||
ServerEvent {
|
||||
source: ServerPtr,
|
||||
event: ServerEvent,
|
||||
},
|
||||
CompletedExchange(SymKey),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(unused)]
|
||||
enum ServerEvent {
|
||||
DeleteKey,
|
||||
SendInitiationRequested,
|
||||
SendRetransmissionRequested,
|
||||
Exchanged(SymKey),
|
||||
DiscardInvalidMessage(anyhow::Error),
|
||||
Transmit(MessageType, SendMsgReason),
|
||||
Receive(Option<MessageType>),
|
||||
DroppedPackage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum SendMsgReason {
|
||||
Initiation,
|
||||
Response,
|
||||
Retransmission,
|
||||
}
|
||||
|
||||
impl TranscriptEvent {
|
||||
fn hibernate() -> Self {
|
||||
Self::Wait(UNENDING)
|
||||
}
|
||||
|
||||
fn begin_poll() -> Self {
|
||||
Self::hibernate()
|
||||
}
|
||||
|
||||
fn transmit(source: ServerPtr, buf: &[u8], reason: SendMsgReason) -> Self {
|
||||
assert!(!buf.is_empty());
|
||||
let msg_type = buf[0];
|
||||
ServerEvent::Transmit(msg_type, reason).into_transcript_event(source)
|
||||
}
|
||||
|
||||
fn receive(source: ServerPtr, buf: &[u8]) -> Self {
|
||||
let msg_type = (!buf.is_empty()).then(|| buf[0]);
|
||||
ServerEvent::Receive(msg_type).into_transcript_event(source)
|
||||
}
|
||||
|
||||
pub fn try_fold_with<F: FnOnce() -> anyhow::Result<TranscriptEvent>>(
|
||||
self,
|
||||
f: F,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
let wait_time_a = match self {
|
||||
Self::Wait(wait_time_a) => wait_time_a,
|
||||
els => return (els).ok(),
|
||||
};
|
||||
|
||||
let wait_time_b = match f()? {
|
||||
Self::Wait(wait_time_b) => wait_time_b,
|
||||
els => return els.ok(),
|
||||
};
|
||||
|
||||
let min_wt = if wait_time_a <= wait_time_b {
|
||||
wait_time_a
|
||||
} else {
|
||||
wait_time_b
|
||||
};
|
||||
Self::Wait(min_wt).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerEvent {
|
||||
fn into_transcript_event(self, source: ServerPtr) -> TranscriptEvent {
|
||||
let event = self;
|
||||
TranscriptEvent::ServerEvent { source, event }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RosenpassSimulator {
|
||||
transcript: Vec<(Timing, TranscriptEvent)>,
|
||||
srv_a: SimulatorServer,
|
||||
srv_b: SimulatorServer,
|
||||
poll_focus: ServerPtr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UpcomingPollResult {
|
||||
IssueEvent(TranscriptEvent),
|
||||
SendMessage(Vec<u8>, TranscriptEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SimulatorServer {
|
||||
/// We sometimes return multiple multiple events in one call,
|
||||
/// but [ServerPtr::poll] should return just one event per call
|
||||
upcoming_poll_results: VecDeque<UpcomingPollResult>,
|
||||
srv: CryptoServer,
|
||||
rx_queue: VecDeque<Vec<u8>>,
|
||||
other_peer: PeerPtr,
|
||||
under_load: bool,
|
||||
}
|
||||
|
||||
impl RosenpassSimulator {
|
||||
/// Set up the simulator
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
// Set up the first server
|
||||
let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.deref_mut())?;
|
||||
let mut srv_a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
|
||||
|
||||
// …and the second server.
|
||||
let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.deref_mut())?;
|
||||
let mut srv_b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
|
||||
|
||||
// Generate a PSK and introduce the Peers to each other.
|
||||
let psk = SymKey::random();
|
||||
let peer_a = srv_a.add_peer(Some(psk.clone()), peer_b_pk)?;
|
||||
let peer_b = srv_b.add_peer(Some(psk), peer_a_pk)?;
|
||||
|
||||
// Set up the individual server data structures
|
||||
let srv_a = SimulatorServer::new(srv_a, peer_b);
|
||||
let srv_b = SimulatorServer::new(srv_b, peer_a);
|
||||
|
||||
// Initialize transcript and polling state
|
||||
let transcript = Vec::new();
|
||||
let poll_focus = ServerPtr::A;
|
||||
|
||||
// Construct the simulator itself
|
||||
Self {
|
||||
transcript,
|
||||
poll_focus,
|
||||
srv_a,
|
||||
srv_b,
|
||||
}
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Call [poll] a fixed number of times
|
||||
fn poll_loop(&mut self, times: u64) -> anyhow::Result<()> {
|
||||
for _ in 0..times {
|
||||
self.poll()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Every call to poll produces one [TranscriptEvent] and
|
||||
/// and implicitly adds it to [Self:::transcript]
|
||||
fn poll(&mut self) -> anyhow::Result<&TranscriptEvent> {
|
||||
let ev = TranscriptEvent::begin_poll()
|
||||
.try_fold_with(|| self.poll_focus.poll(self))?
|
||||
.try_fold_with(|| {
|
||||
self.poll_focus = self.poll_focus.other();
|
||||
self.poll_focus.poll(self)
|
||||
})?;
|
||||
|
||||
// Generate up a time stamp
|
||||
let now = self.srv_a.srv.timebase.now();
|
||||
|
||||
// Push the event onto the transcript
|
||||
self.transcript.push((now, ev));
|
||||
// We can unwrap; we just pushed the event ourselves
|
||||
let ev = self.transcript.last().unwrap().1.borrow();
|
||||
|
||||
// Time travel instead of waiting
|
||||
if let TranscriptEvent::Wait(secs) = ev {
|
||||
time_travel_forward(&mut self.srv_a.srv, *secs);
|
||||
time_travel_forward(&mut self.srv_b.srv, *secs);
|
||||
}
|
||||
|
||||
ev.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl SimulatorServer {
|
||||
fn new(srv: CryptoServer, other_peer: PeerPtr) -> Self {
|
||||
let upcoming_poll_results = VecDeque::new();
|
||||
let rx_queue = VecDeque::new();
|
||||
let under_load = false;
|
||||
Self {
|
||||
upcoming_poll_results,
|
||||
srv,
|
||||
rx_queue,
|
||||
other_peer,
|
||||
under_load,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Straightforward way of accessing either of the two servers
|
||||
/// with associated data
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ServerPtr {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServerPtr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("{:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl HostIdentification for ServerPtr {
|
||||
fn encode(&self) -> &[u8] {
|
||||
match *self {
|
||||
Self::A => b"ServerPtr::A",
|
||||
Self::B => b"ServerPtr::B",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerPtr {
|
||||
fn poll(self, sim: &mut RosenpassSimulator) -> anyhow::Result<TranscriptEvent> {
|
||||
TranscriptEvent::begin_poll()
|
||||
.try_fold_with(|| self.flush_upcoming_events(sim).ok())?
|
||||
.try_fold_with(|| self.poll_for_timed_events(sim))?
|
||||
.try_fold_with(|| self.process_incoming_messages(sim))
|
||||
}
|
||||
|
||||
/// Returns and applies the first upcoming event
|
||||
fn flush_upcoming_events(self, sim: &mut RosenpassSimulator) -> TranscriptEvent {
|
||||
use UpcomingPollResult as R;
|
||||
match self.get_mut(sim).upcoming_poll_results.pop_front() {
|
||||
None => TranscriptEvent::hibernate(),
|
||||
Some(R::IssueEvent(ev)) => ev,
|
||||
Some(R::SendMessage(msg, ev)) => {
|
||||
self.transmit(sim, msg);
|
||||
ev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_for_timed_events(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
use PollResult as P;
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
let other_peer = self.peer(sim);
|
||||
|
||||
// Check if there are events to process from poll()
|
||||
loop {
|
||||
match self.srv_mut(sim).poll()? {
|
||||
// Poll just told us to immediately call poll again
|
||||
P::Sleep(0.0) => continue,
|
||||
|
||||
// No event to handle immediately. We can now check to see if there are some
|
||||
// messages to be handled
|
||||
P::Sleep(wait_time) => {
|
||||
return TE::Wait(wait_time).ok();
|
||||
}
|
||||
|
||||
// Not deleting any keys in practice here, since we just push events to the
|
||||
// transcript
|
||||
P::DeleteKey(_) => {
|
||||
return SE::DeleteKey.into_transcript_event(self).ok();
|
||||
}
|
||||
|
||||
P::SendInitiation(_) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::SendInitiationRequested.into_transcript_event(self),
|
||||
);
|
||||
|
||||
let mut buf = MsgBuf::zero();
|
||||
let len = self
|
||||
.srv_mut(sim)
|
||||
.initiate_handshake(other_peer, &mut buf[..])?;
|
||||
self.enqueue_upcoming_poll_transmission(
|
||||
sim,
|
||||
buf[..len].to_vec(),
|
||||
SendMsgReason::Initiation,
|
||||
);
|
||||
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
|
||||
P::SendRetransmission(_) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::SendRetransmissionRequested.into_transcript_event(self),
|
||||
);
|
||||
|
||||
let mut buf = MsgBuf::zero();
|
||||
let len = self
|
||||
.srv_mut(sim)
|
||||
.retransmit_handshake(other_peer, &mut buf[..])?;
|
||||
self.enqueue_upcoming_poll_transmission(
|
||||
sim,
|
||||
buf[..len].to_vec(),
|
||||
SendMsgReason::Retransmission,
|
||||
);
|
||||
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn process_incoming_messages(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
) -> anyhow::Result<TranscriptEvent> {
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
// Check for a message or exit
|
||||
let rx_msg = match self.recv(sim) {
|
||||
None => return TE::hibernate().ok(),
|
||||
// Actually received a message
|
||||
Some(rx_msg) => rx_msg,
|
||||
};
|
||||
|
||||
// Add info that a message was received into the transcript
|
||||
self.enqueue_upcoming_poll_event(sim, TE::receive(self, rx_msg.borrow()));
|
||||
|
||||
// Let the crypto server handle the message now
|
||||
let mut tx_buf = MsgBuf::zero();
|
||||
let handle_msg_result = if self.get(sim).under_load {
|
||||
self.srv_mut(sim)
|
||||
.handle_msg_under_load(rx_msg.borrow(), tx_buf.borrow_mut(), &self)
|
||||
} else {
|
||||
self.srv_mut(sim)
|
||||
.handle_msg(rx_msg.borrow(), tx_buf.borrow_mut())
|
||||
};
|
||||
|
||||
// Handle bad messages
|
||||
let handle_msg_result = match handle_msg_result {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::DiscardInvalidMessage(e).into_transcript_event(self),
|
||||
);
|
||||
return self.flush_upcoming_events(sim).ok(); // Just added them
|
||||
}
|
||||
};
|
||||
|
||||
// Successful key exchange; emit the appropriate event
|
||||
if handle_msg_result.exchanged_with.is_some() {
|
||||
self.enqueue_on_exchanged_events(sim)?;
|
||||
}
|
||||
|
||||
// Handle message responses
|
||||
if let Some(len) = handle_msg_result.resp {
|
||||
let resp = &tx_buf[..len];
|
||||
self.enqueue_upcoming_poll_transmission(sim, resp.to_vec(), SendMsgReason::Response);
|
||||
};
|
||||
|
||||
// Return the first of the events we just enqueued
|
||||
self.flush_upcoming_events(sim).ok()
|
||||
}
|
||||
|
||||
fn enqueue_on_exchanged_events(self, sim: &mut RosenpassSimulator) -> anyhow::Result<()> {
|
||||
use ServerEvent as SE;
|
||||
use TranscriptEvent as TE;
|
||||
|
||||
// Retrieve the key exchanged; this function will panic if the OSK is missing
|
||||
let osk = self.osk(sim).unwrap();
|
||||
|
||||
// Issue the `Exchanged`
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
SE::Exchanged(osk.clone()).into_transcript_event(self),
|
||||
);
|
||||
|
||||
// Retrieve the other osk
|
||||
let other_osk = match self.other().try_osk(sim) {
|
||||
Some(other_osk) => other_osk,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Issue the successful exchange event if the OSKs are equal;
|
||||
// be careful to use constant time comparison for things like this!
|
||||
if rosenpass_constant_time::memcmp(osk.secret(), other_osk.secret()) {
|
||||
self.enqueue_upcoming_poll_event(sim, TE::CompletedExchange(osk));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enqueue_upcoming_poll_event(self, sim: &mut RosenpassSimulator, ev: TranscriptEvent) {
|
||||
let upcoming = UpcomingPollResult::IssueEvent(ev);
|
||||
self.get_mut(sim).upcoming_poll_results.push_back(upcoming);
|
||||
}
|
||||
|
||||
fn enqueue_upcoming_poll_transmission(
|
||||
self,
|
||||
sim: &mut RosenpassSimulator,
|
||||
msg: Vec<u8>,
|
||||
reason: SendMsgReason,
|
||||
) {
|
||||
let ev = TranscriptEvent::transmit(self, msg.borrow(), reason);
|
||||
let upcoming = UpcomingPollResult::SendMessage(msg, ev);
|
||||
self.get_mut(sim).upcoming_poll_results.push_back(upcoming);
|
||||
}
|
||||
|
||||
fn try_osk(self, sim: &RosenpassSimulator) -> Option<SymKey> {
|
||||
let peer = self.peer(sim);
|
||||
let has_osk = peer.session().get(self.srv(sim)).is_some();
|
||||
|
||||
has_osk.then(|| {
|
||||
// We already checked whether the OSK is present; there should be no other errors
|
||||
self.osk(sim).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn osk(self, sim: &RosenpassSimulator) -> anyhow::Result<SymKey> {
|
||||
self.srv(sim).osk(self.peer(sim))
|
||||
}
|
||||
|
||||
fn drop_outgoing_packet(self, sim: &mut RosenpassSimulator) -> Option<Vec<u8>> {
|
||||
let pkg = self.tx_queue_mut(sim).pop_front();
|
||||
self.enqueue_upcoming_poll_event(
|
||||
sim,
|
||||
ServerEvent::DroppedPackage.into_transcript_event(self),
|
||||
);
|
||||
pkg
|
||||
}
|
||||
|
||||
fn other(self) -> Self {
|
||||
match self {
|
||||
Self::A => Self::B,
|
||||
Self::B => Self::A,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(self, sim: &RosenpassSimulator) -> &SimulatorServer {
|
||||
match self {
|
||||
ServerPtr::A => sim.srv_a.borrow(),
|
||||
ServerPtr::B => sim.srv_b.borrow(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(self, sim: &mut RosenpassSimulator) -> &mut SimulatorServer {
|
||||
match self {
|
||||
ServerPtr::A => sim.srv_a.borrow_mut(),
|
||||
ServerPtr::B => sim.srv_b.borrow_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
fn srv(self, sim: &RosenpassSimulator) -> &CryptoServer {
|
||||
self.get(sim).srv.borrow()
|
||||
}
|
||||
|
||||
fn srv_mut(self, sim: &mut RosenpassSimulator) -> &mut CryptoServer {
|
||||
self.get_mut(sim).srv.borrow_mut()
|
||||
}
|
||||
|
||||
fn peer(self, sim: &RosenpassSimulator) -> PeerPtr {
|
||||
self.get(sim).other_peer
|
||||
}
|
||||
|
||||
fn recv(self, sim: &mut RosenpassSimulator) -> Option<Vec<u8>> {
|
||||
self.rx_queue_mut(sim).pop_front()
|
||||
}
|
||||
|
||||
fn transmit(self, sim: &mut RosenpassSimulator, msg: Vec<u8>) {
|
||||
self.tx_queue_mut(sim).push_back(msg);
|
||||
}
|
||||
|
||||
fn rx_queue_mut(self, sim: &mut RosenpassSimulator) -> &mut VecDeque<Vec<u8>> {
|
||||
self.get_mut(sim).rx_queue.borrow_mut()
|
||||
}
|
||||
|
||||
fn tx_queue_mut(self, sim: &mut RosenpassSimulator) -> &mut VecDeque<Vec<u8>> {
|
||||
self.other().rx_queue_mut(sim)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,11 @@ use std::{iter::Peekable, net::SocketAddr};
|
||||
|
||||
use crate::exchange::{ExchangeOptions, ExchangePeer};
|
||||
|
||||
/// The different commands supported by the `rp` binary.
|
||||
/// [GenKey](crate::cli::Command::GenKey), [PubKey](crate::cli::Command::PubKey),
|
||||
/// [Exchange](crate::cli::Command::Exchange) and
|
||||
/// [ExchangeConfig](crate::cli::Command::ExchangeConfig)
|
||||
/// contain information specific to the respective command.
|
||||
pub enum Command {
|
||||
GenKey {
|
||||
private_keys_dir: PathBuf,
|
||||
@@ -18,6 +23,10 @@ pub enum Command {
|
||||
Help,
|
||||
}
|
||||
|
||||
/// The different command types supported by the `rp` binary.
|
||||
/// This enum is exclusively used in [fatal] and when calling [fatal] and is therefore
|
||||
/// limited to the command types that can fail. E.g., the help command can not fail and is therefore
|
||||
/// not part of the [CommandType]-enum.
|
||||
enum CommandType {
|
||||
GenKey,
|
||||
PubKey,
|
||||
@@ -25,12 +34,24 @@ enum CommandType {
|
||||
ExchangeConfig,
|
||||
}
|
||||
|
||||
/// This structure captures the result of parsing the arguments to the `rp` binary.
|
||||
/// A new [Cli] is created by calling [Cli::parse] with the appropriate arguments.
|
||||
#[derive(Default)]
|
||||
pub struct Cli {
|
||||
/// Whether the output should be verbose.
|
||||
pub verbose: bool,
|
||||
/// The command specified by the given arguments.
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
/// Processes a fatal error when parsing cli arguments.
|
||||
/// It *always* returns an [Err(String)], where such that the contained [String] explains
|
||||
/// the parsing error, including the provided `note`.
|
||||
///
|
||||
/// # Generic Parameters
|
||||
/// the generic parameter `T` is given to make the [Result]-type compatible with the respective
|
||||
/// return type of the calling function.
|
||||
///
|
||||
fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
||||
match command {
|
||||
Some(command) => match command {
|
||||
@@ -44,6 +65,9 @@ fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
||||
}
|
||||
|
||||
impl ExchangePeer {
|
||||
/// Parses peer parameters given to the `rp` binary in the context of an `exchange` operation.
|
||||
/// It returns a result with either [ExchangePeer] that contains the parameters of the peer
|
||||
/// or an error describing why the arguments could not be parsed.
|
||||
pub fn parse(args: &mut &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut peer = ExchangePeer::default();
|
||||
|
||||
@@ -126,6 +150,9 @@ impl ExchangePeer {
|
||||
}
|
||||
|
||||
impl ExchangeOptions {
|
||||
/// Parses the arguments given to the `rp` binary *if the `exchange` operation is given*.
|
||||
/// It returns a result with either [ExchangeOptions] that contains the result of parsing the
|
||||
/// arguments or an error describing why the arguments could not be parsed.
|
||||
pub fn parse(mut args: &mut Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut options = ExchangeOptions::default();
|
||||
|
||||
@@ -191,6 +218,9 @@ impl ExchangeOptions {
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
/// Parses the arguments given to the `rp` binary. It returns a result with either
|
||||
/// a [Cli] that contains the result of parsing the arguments or an error describing
|
||||
/// why the arguments could not be parsed.
|
||||
pub fn parse(mut args: Peekable<impl Iterator<Item = String>>) -> Result<Self, String> {
|
||||
let mut cli = Cli::default();
|
||||
|
||||
|
||||
@@ -11,21 +11,37 @@ use anyhow::Result;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use crate::key::WG_B64_LEN;
|
||||
|
||||
/// Used to define a peer for the rosenpass connection that consists of
|
||||
/// a directory for storing public keys and optionally an IP address and port of the endpoint,
|
||||
/// for how long the connection should be kept alive and a list of allowed IPs for the peer.
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangePeer {
|
||||
/// Directory where public keys are stored
|
||||
pub public_keys_dir: PathBuf,
|
||||
/// The IP address of the endpoint
|
||||
pub endpoint: Option<SocketAddr>,
|
||||
/// For how long to keep the connection alive
|
||||
pub persistent_keepalive: Option<u32>,
|
||||
/// The IPs that are allowed for this peer.
|
||||
pub allowed_ips: Option<String>,
|
||||
}
|
||||
|
||||
/// Options for the exchange operation of the `rp` binary.
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangeOptions {
|
||||
/// Whether the cli output should be verbose.
|
||||
pub verbose: bool,
|
||||
/// path to the directory where private keys are stored.
|
||||
pub private_keys_dir: PathBuf,
|
||||
/// The link rosenpass should run as. If None is given [exchange] will use `"rosenpass0"`
|
||||
/// instead.
|
||||
pub dev: Option<String>,
|
||||
/// The IP-address rosenpass should run under.
|
||||
pub ip: Option<String>,
|
||||
/// The IP-address and port that the rosenpass [AppServer](rosenpass::app_server::AppServer)
|
||||
/// should use.
|
||||
pub listen: Option<SocketAddr>,
|
||||
/// Other peers a connection should be initialized to
|
||||
pub peers: Vec<ExchangePeer>,
|
||||
}
|
||||
|
||||
@@ -48,8 +64,11 @@ mod netlink {
|
||||
use netlink_packet_wireguard::nlas::WgDeviceAttrs;
|
||||
use rtnetlink::Handle;
|
||||
|
||||
/// Creates a netlink named `link_name` and changes the state to up. It returns the index
|
||||
/// of the interface in the list of interfaces as the result or an error if any of the
|
||||
/// operations of creating the link or changing its state to up fails.
|
||||
pub async fn link_create_and_up(rtnetlink: &Handle, link_name: String) -> Result<u32> {
|
||||
// add the link
|
||||
// Add the link, equivalent to `ip link add <link_name> type wireguard`.
|
||||
rtnetlink
|
||||
.link()
|
||||
.add()
|
||||
@@ -57,7 +76,8 @@ mod netlink {
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
// retrieve the link to be able to up it
|
||||
// Retrieve the link to be able to up it, equivalent to `ip link show` and then
|
||||
// using the link shown that is identified by `link_name`.
|
||||
let link = rtnetlink
|
||||
.link()
|
||||
.get()
|
||||
@@ -69,7 +89,7 @@ mod netlink {
|
||||
.0
|
||||
.unwrap()?;
|
||||
|
||||
// up the link
|
||||
// Up the link, equivalent to `ip link set dev <DEV> up`.
|
||||
rtnetlink
|
||||
.link()
|
||||
.set(link.header.index)
|
||||
@@ -80,12 +100,16 @@ mod netlink {
|
||||
Ok(link.header.index)
|
||||
}
|
||||
|
||||
/// Deletes a link using rtnetlink. The link is specified using its index in the list of links.
|
||||
pub async fn link_cleanup(rtnetlink: &Handle, index: u32) -> Result<()> {
|
||||
rtnetlink.link().del(index).execute().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes a link using rtnetlink. The link is specified using its index in the list of links.
|
||||
/// In contrast to [link_cleanup], this function create a new socket connection to netlink and
|
||||
/// *ignores errors* that occur during deletion.
|
||||
pub async fn link_cleanup_standalone(index: u32) -> Result<()> {
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
@@ -111,7 +135,7 @@ mod netlink {
|
||||
use netlink_packet_generic::GenlMessage;
|
||||
use netlink_packet_wireguard::{Wireguard, WireguardCmd};
|
||||
|
||||
// Scope our `set` command to only the device of the specified index
|
||||
// Scope our `set` command to only the device of the specified index.
|
||||
attr.insert(0, WgDeviceAttrs::IfIndex(index));
|
||||
|
||||
// Construct the WireGuard-specific netlink packet
|
||||
@@ -120,12 +144,12 @@ mod netlink {
|
||||
nlas: attr,
|
||||
};
|
||||
|
||||
// Construct final message
|
||||
// Construct final message.
|
||||
let genl = GenlMessage::from_payload(wgc);
|
||||
let mut nlmsg = NetlinkMessage::from(genl);
|
||||
nlmsg.header.flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||
|
||||
// Send and wait for the ACK or error
|
||||
// Send and wait for the ACK or error.
|
||||
let (res, _) = genetlink.request(nlmsg).await?.into_future().await;
|
||||
if let Some(res) = res {
|
||||
let res = res?;
|
||||
@@ -138,6 +162,9 @@ mod netlink {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a list of cleanup handlers that can be used in an asynchronous context
|
||||
/// to clean up after the usage of rosenpass or if the `rp` binary is interrupted with ctrl+c
|
||||
/// or a `SIGINT` signal in general.
|
||||
#[derive(Clone)]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
struct CleanupHandlers(
|
||||
@@ -146,19 +173,27 @@ struct CleanupHandlers(
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
impl CleanupHandlers {
|
||||
/// Creates a new list of [CleanupHandlers].
|
||||
fn new() -> Self {
|
||||
CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![])))
|
||||
}
|
||||
|
||||
/// Enqueues a new cleanup handler in the form of a [Future].
|
||||
async fn enqueue(&self, handler: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>) {
|
||||
self.0.lock().await.push(Box::pin(handler))
|
||||
}
|
||||
|
||||
/// Runs all cleanup handlers. Following the documentation of [futures::future::try_join_all]:
|
||||
/// If any cleanup handler returns an error then all other cleanup handlers will be canceled and
|
||||
/// an error will be returned immediately. If all cleanup handlers complete successfully,
|
||||
/// however, then the returned future will succeed with a Vec of all the successful results.
|
||||
async fn run(self) -> Result<Vec<()>, Error> {
|
||||
futures::future::try_join_all(self.0.lock().await.deref_mut()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the rosenpass link and wireguard and configures both with the configuration specified by
|
||||
/// `options`.
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
use std::fs;
|
||||
@@ -182,6 +217,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
||||
|
||||
// Set up a list of (initiallc empty) cleanup handlers that are to be run if
|
||||
// ctrl-c is hit or generally a `SIGINT` signal is received and always in the end.
|
||||
let cleanup_handlers = CleanupHandlers::new();
|
||||
let final_cleanup_handlers = (&cleanup_handlers).clone();
|
||||
|
||||
@@ -198,6 +235,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
.expect("Failed to clean up");
|
||||
})?;
|
||||
|
||||
// Run `ip address add <ip> dev <dev>` and enqueue `ip address del <ip> dev <dev>` as a cleanup.
|
||||
if let Some(ip) = options.ip {
|
||||
let dev = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||
Command::new("ip")
|
||||
@@ -223,7 +261,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
.await;
|
||||
}
|
||||
|
||||
// Deploy the classic wireguard private key
|
||||
// Deploy the classic wireguard private key.
|
||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
@@ -244,6 +282,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
|
||||
netlink::wg_set(&mut genetlink, link_index, attr).await?;
|
||||
|
||||
// set up the rosenpass AppServer
|
||||
let pqsk = options.private_keys_dir.join("pqsk");
|
||||
let pqpk = options.private_keys_dir.join("pqpk");
|
||||
|
||||
@@ -271,6 +310,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e))
|
||||
}
|
||||
|
||||
// Configure everything per peer.
|
||||
for peer in options.peers {
|
||||
let wgpk = peer.public_keys_dir.join("wgpk");
|
||||
let pqpk = peer.public_keys_dir.join("pqpk");
|
||||
@@ -318,7 +358,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
peer.endpoint.map(|x| x.to_string()),
|
||||
)?;
|
||||
|
||||
// Configure routes
|
||||
// Configure routes, equivalent to `ip route replace <allowed_ips> dev <dev>` and set up
|
||||
// the cleanup as `ip route del <allowed_ips>`.
|
||||
if let Some(allowed_ips) = peer.allowed_ips {
|
||||
Command::new("ip")
|
||||
.arg("route")
|
||||
@@ -349,7 +390,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
match out {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
// Check if the returned error is actually EINTR, in which case, the run actually succeeded.
|
||||
// Check if the returned error is actually EINTR, in which case, the run actually
|
||||
// succeeded.
|
||||
let is_ok = if let Some(e) = e.root_cause().downcast_ref::<std::io::Error>() {
|
||||
matches!(e.kind(), std::io::ErrorKind::Interrupted)
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};
|
||||
|
||||
/// The length of wireguard keys as a length in base 64 encoding.
|
||||
pub const WG_B64_LEN: usize = 32 * 5 / 3;
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
@@ -24,6 +25,14 @@ pub fn genkey(_: &Path) -> Result<()> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Generates a new symmetric keys for wireguard and asymmetric keys for rosenpass
|
||||
/// in the provided `private_keys_dir`.
|
||||
///
|
||||
/// It checks whether the directory `private_keys_dir` points to exists and creates it otherwise.
|
||||
/// If it exists, it ensures that the permission is set to 0700 and aborts otherwise. If the
|
||||
/// directory is newly created, the appropriate permissions are set.
|
||||
///
|
||||
/// Already existing keys are not overwritten.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
||||
if private_keys_dir.exists() {
|
||||
@@ -70,6 +79,11 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new directory under `public_keys_dir` and stores the public keys for rosenpass and for
|
||||
/// wireguard that correspond to the private keys in `private_keys_dir` in `public_keys_dir`.
|
||||
///
|
||||
/// If `public_keys_dir` already exists, the wireguard private key or the rosenpass public key
|
||||
/// are not present in `private_keys_dir`, an error is returned.
|
||||
pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
||||
if public_keys_dir.exists() {
|
||||
return Err(anyhow!("Directory {:?} already exists", public_keys_dir));
|
||||
@@ -90,9 +104,11 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> {
|
||||
Public::from_slice(public.as_bytes())
|
||||
};
|
||||
|
||||
// Store the wireguard public key.
|
||||
wgpk.store_b64::<WG_B64_LEN, _>(public_wgpk)?;
|
||||
wgpk.zeroize();
|
||||
|
||||
// Copy the pq-public key to the public directory.
|
||||
fs::copy(private_pqpk, public_pqpk)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
|
||||
use std::ops::BitXorAssign;
|
||||
|
||||
// Destination functions return some value that implements the To trait.
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would
|
||||
// be without destination parameters
|
||||
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: BitXorAssign + Clone,
|
||||
{
|
||||
// Custom implementations of the to trait can be created, but the easiest
|
||||
// way to create them is to use the provided helper functions like with_destination.
|
||||
with_destination(move |dst: &mut [T]| {
|
||||
assert!(src.len() == dst.len());
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Functions with destination copying data between slices and arrays.
|
||||
//! Functions that make it easy to copy data between arrays and slices using functions with
|
||||
//! destinations. See the specific functions for examples and more explanations.
|
||||
|
||||
use crate::{with_destination, To};
|
||||
|
||||
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the two slices have different lengths.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice;
|
||||
/// let to_function = copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -23,6 +35,19 @@ where
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if destination is shorter than origin.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least_src;
|
||||
/// let to_function = copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -34,6 +59,18 @@ where
|
||||
/// destination.
|
||||
///
|
||||
/// Copies as much data as is present in the shorter slice.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least;
|
||||
/// let to_function = copy_slice_least(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -47,6 +84,24 @@ where
|
||||
/// Function with destination that attempts to copy data from origin into the destination.
|
||||
///
|
||||
/// Will return None if the slices are of different lengths.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice;
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the slices do not have the same length.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -62,6 +117,26 @@ where
|
||||
/// Destination may be longer than origin.
|
||||
///
|
||||
/// Will return None if the destination is shorter than origin.
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice_least_src;
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 15];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the destination is to short.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation, the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -72,6 +147,18 @@ where
|
||||
}
|
||||
|
||||
/// Function with destination that copies all data between two array references.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::ops::copy_array;
|
||||
/// use rosenpass_to::To;
|
||||
/// let my_arr: [u8; 32] = [0; 32];
|
||||
/// let to_function = copy_array(&my_arr);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
//! This module provides the [Beside] struct. In the context of functions with targets,
|
||||
//! [Beside] structures the destination value and the return value unmistakably and offers useful
|
||||
//! helper functions to work with them.
|
||||
|
||||
use crate::CondenseBeside;
|
||||
|
||||
/// Named tuple holding the return value and the output from a function with destinations.
|
||||
/// Named tuple holding the return value and the destination from a function with destinations.
|
||||
/// See the respective functions for usage examples.
|
||||
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||
|
||||
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
/// Perform beside condensation. See [CondenseBeside]
|
||||
/// Perform beside condensation. See [CondenseBeside] for more details.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
|
||||
(val, ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Beside;
|
||||
|
||||
#[test]
|
||||
fn from_tuple() {
|
||||
let tuple = (21u8, 42u16);
|
||||
let beside: Beside<u8, u16> = Beside::from(tuple);
|
||||
assert_eq!(beside.dest(), &21u8);
|
||||
assert_eq!(beside.ret(), &42u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_beside() {
|
||||
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
|
||||
type U8u16 = (u8, u16);
|
||||
let tuple = U8u16::from(beside);
|
||||
assert_eq!(tuple.0, 21u8);
|
||||
assert_eq!(tuple.1, 42u16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/// Beside condensation.
|
||||
//! This module provides condensation for values that stand side by side,
|
||||
//! which is often useful when working with destination parameters. See [CondenseBeside]
|
||||
//! for more details.
|
||||
|
||||
/// Condenses two values that stand beside each other into one value.
|
||||
/// For example, a blanked implementation for [Result<(), Error>](Result) is provided. If
|
||||
/// `condense(val)` is called on such an object, a [Result<Val, Error>](Result) will
|
||||
/// be returned, if `val` is of type `Val`.
|
||||
///
|
||||
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
|
||||
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
|
||||
@@ -6,6 +13,19 @@
|
||||
///
|
||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||
/// condense trait.
|
||||
///
|
||||
/// # Example
|
||||
/// As an example implementation, we take a look at the blanket implementation for [Option]
|
||||
/// ```ignore
|
||||
/// impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
/// type Condensed = Option<Val>;
|
||||
///
|
||||
/// /// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
/// fn condense(self, ret: Val) -> Option<Val> {
|
||||
/// self.map(|()| ret)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait CondenseBeside<Val> {
|
||||
/// The type that results from condensation.
|
||||
type Condensed;
|
||||
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
|
||||
impl<Val> CondenseBeside<Val> for () {
|
||||
type Condensed = Val;
|
||||
|
||||
/// Replaces this empty tuple with `ret`.
|
||||
fn condense(self, ret: Val) -> Val {
|
||||
ret
|
||||
}
|
||||
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
|
||||
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
type Condensed = Result<Val, Error>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Result] with `ret`.
|
||||
fn condense(self, ret: Val) -> Result<Val, Error> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
type Condensed = Option<Val>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
fn condense(self, ret: Val) -> Option<Val> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
/// Helper performing explicit unsized coercion.
|
||||
/// Used by the [to](crate::to()) function.
|
||||
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
|
||||
//! types. See [DstCoercion] for more details.
|
||||
|
||||
/// Helper Trait for performing explicit coercion from [Sized] types to
|
||||
/// [?Sized][core::marker::Sized] types. It's used by the [to](crate::to()) function.
|
||||
///
|
||||
/// We provide blanket implementations for any [Sized] type and for any array of [Sized] types.
|
||||
///
|
||||
/// # Example
|
||||
/// It can be used as follows:
|
||||
/// ```
|
||||
/// # use rosenpass_to::DstCoercion;
|
||||
/// // Consider a sized type like this example:
|
||||
/// struct SizedStruct {
|
||||
/// x: u32
|
||||
/// }
|
||||
/// // Then we can coerce it to be unsized:
|
||||
/// let mut sized = SizedStruct { x: 42 };
|
||||
/// assert_eq!(42, sized.coerce_dest().x);
|
||||
///
|
||||
/// // Analogously, we can coerce arrays to slices:
|
||||
/// let mut sized_array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
/// let un_sized: &[i32] = sized_array.coerce_dest();
|
||||
/// assert_eq!(un_sized, un_sized);
|
||||
/// ```
|
||||
pub trait DstCoercion<Dst: ?Sized> {
|
||||
/// Performs an explicit coercion to the destination type.
|
||||
fn coerce_dest(&mut self) -> &mut Dst;
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
||||
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
||||
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or
|
||||
//! some sized variant of
|
||||
//! `Dst` (e.g. `[u8; 64]`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The
|
||||
//! ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as
|
||||
//! `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>)
|
||||
//! – The combiation of Val and Ret after condensing was applied
|
||||
//! (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
|
||||
pub mod beside;
|
||||
pub mod condense;
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
//! This module provides the [To::to] function which allows to use functions with destination in
|
||||
//! a manner akin to that of a variable assignment. See [To::to] for more details.
|
||||
|
||||
use crate::{DstCoercion, To};
|
||||
|
||||
/// Alias for [To::to] moving the destination to the left.
|
||||
///
|
||||
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
|
||||
/// the variable to assign to on the left and the generating function on the right.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// // Using the to function to have data flowing from the right to the left,
|
||||
/// // performing something akin to a variable assignment.
|
||||
/// use rosenpass_to::ops::copy_slice_least;
|
||||
/// # use rosenpass_to::to;
|
||||
/// let mut dst = b" ".to_vec();
|
||||
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
|
||||
/// assert_eq!(&dst[..], b"Hello World");
|
||||
/// ```
|
||||
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
|
||||
where
|
||||
Coercable: ?Sized + DstCoercion<Dst>,
|
||||
|
||||
@@ -1,12 +1,46 @@
|
||||
//! Module that contains the [To] crate which is the container used to
|
||||
//! implement the core functionality of this crate.
|
||||
|
||||
use crate::{Beside, CondenseBeside};
|
||||
use std::borrow::BorrowMut;
|
||||
|
||||
/// The To trait is the core of the to crate; most functions with destinations will either return
|
||||
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
||||
/// Return_value`.
|
||||
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
|
||||
/// Return_value>`.
|
||||
///
|
||||
/// A quick way to implement a function with destination is to use the
|
||||
/// [with_destination(|param: &mut Type| ...)] higher order function.
|
||||
/// [with_destination(|param: &mut Type| ...)](crate::with_destination) higher order function.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a very simple example for how the Trait can be implemented. More examples for
|
||||
/// how this Trait is best implemented can be found in the overall [crate documentation](crate).
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
///
|
||||
/// // This is a simple wrapper around a String that can be written into a byte array using to.
|
||||
/// struct StringToBytes {
|
||||
/// inner: String
|
||||
/// }
|
||||
///
|
||||
/// impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// let bytes = self.inner.as_bytes();
|
||||
/// if bytes.len() > out.len() {
|
||||
/// return Err("out is to short".to_string());
|
||||
/// }
|
||||
/// for i in 0..bytes.len() {
|
||||
/// (*out)[i] = bytes[i];
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let mut buffer: [u8; 10] = [0; 10];
|
||||
/// let result = string_to_bytes.to(&mut buffer);
|
||||
/// assert_eq!(buffer, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
||||
///
|
||||
@@ -19,6 +53,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [self.to_this_beside]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let Beside(dst, result) = string_to_bytes.to_this_beside(|| [0; 10]);
|
||||
/// assert_eq!(dst, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: BorrowMut<Dst>,
|
||||
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
|
||||
/// Generate a destination on the fly using default.
|
||||
///
|
||||
/// Uses [Default] to create a value,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value_beside](To::to_value_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let Beside(dst, ret) = copy_array(&[42u8; 16]).to_value_beside();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value_beside(self) -> Beside<Dst, Ret>
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// when the Destination is unsized.
|
||||
///
|
||||
/// This could be the case when the destination is an `[u8]` for instance.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect_beside](To::collect_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let Beside(dst, ret) = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect_beside<Val>(self) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [Self::to_this]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let result = string_to_bytes.to_this_beside(|| [0; 10]).condense();
|
||||
/// assert!(result.is_ok());
|
||||
/// assert_eq!(result.unwrap(), [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
///
|
||||
/// ```
|
||||
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Ret: CondenseBeside<Val>,
|
||||
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value](To::to_value).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let dst = copy_array(&[42u8; 16]).to_value_beside().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect](To::collect).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
//! The module provides the [with_destination] function, which makes it easy to create
|
||||
//! a [To] from a lambda function. See [with_destination] and the [crate documentation](crate)
|
||||
//! for more details and examples.
|
||||
|
||||
use crate::To;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A struct that wraps a closure and implements the `To` trait
|
||||
/// A struct that wraps a closure and implements the `To` trait.
|
||||
///
|
||||
/// This allows passing closures that operate on a destination type `Dst`
|
||||
/// and return `Ret`.
|
||||
/// and return `Ret`. It is only internally used to implement [with_destination].
|
||||
///
|
||||
/// # Type Parameters
|
||||
/// * `Dst` - The destination type the closure operates on
|
||||
/// * `Ret` - The return type of the closure
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||
/// * `Dst` - The destination type the closure operates on.
|
||||
/// * `Ret` - The return type of the closure.
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
|
||||
struct ToClosure<Dst, Ret, Fun>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
@@ -17,11 +21,11 @@ where
|
||||
{
|
||||
/// The function to call.
|
||||
fun: Fun,
|
||||
/// Phantom data to hold the destination type
|
||||
/// Phantom data to hold the destination type.
|
||||
_val: PhantomData<Box<Dst>>,
|
||||
}
|
||||
|
||||
/// Implementation of the `To` trait for ToClosure
|
||||
/// Implementation of the `To` trait for ToClosure.
|
||||
///
|
||||
/// This enables calling the wrapped closure with a destination reference.
|
||||
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||
@@ -33,6 +37,7 @@ where
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `out` - Mutable reference to the destination
|
||||
/// See the tutorial in [readme.md] for examples and more explanations.
|
||||
fn to(self, out: &mut Dst) -> Ret {
|
||||
(self.fun)(out)
|
||||
}
|
||||
@@ -48,7 +53,24 @@ where
|
||||
/// * `Ret` - The return type of the closure
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||
///
|
||||
/// See the tutorial in [readme.me]..
|
||||
/// See the tutorial in the [crate documentation](crate) for more examples and more explanations.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_to::with_destination;
|
||||
/// use crate::rosenpass_to::To;
|
||||
/// let my_origin_data: [u8; 16]= [2; 16];
|
||||
/// let times_two = with_destination( move |dst: &mut [u8; 16]| {
|
||||
/// for (dst, org) in dst.iter_mut().zip(my_origin_data.iter()) {
|
||||
/// *dst = dst.clone() * org;
|
||||
/// }
|
||||
/// });
|
||||
/// let mut dst: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||||
/// times_two.to(&mut dst);
|
||||
/// for i in 0..16 {
|
||||
/// assert_eq!(dst[i], (2 * i) as u8);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
|
||||
@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
|
||||
|
||||
/// A type that can be incrementally built from a type that can [Build] it
|
||||
///
|
||||
/// This is similar to an option, where [Self::Void] is [std::Option::None],
|
||||
/// [Self::Product] is [std::Option::Some], except that there is a third
|
||||
/// This is similar to an option, where [Self::Void] is [std::option::Option::None],
|
||||
/// [Self::Product] is [std::option::Option::Some], except that there is a third
|
||||
/// intermediate state [Self::Builder] that represents a Some/Product value
|
||||
/// in the process of being made.
|
||||
///
|
||||
@@ -508,9 +508,9 @@ where
|
||||
matches!(self, Self::Void)
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`InProgress`].
|
||||
/// Returns `true` if the construction site is in the [`Builder`] phase.
|
||||
///
|
||||
/// [`InProgress`]: ConstructionSite::InProgress
|
||||
/// [`Builder`]: ConstructionSite::Builder
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -541,9 +541,10 @@ where
|
||||
matches!(self, Self::Builder(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`Done`].
|
||||
/// Returns `true` if the construction site is in the [`Product`] phase and
|
||||
/// is therefore done.
|
||||
///
|
||||
/// [`Done`]: ConstructionSite::Done
|
||||
/// [`Product`]: ConstructionSite::Product
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// A collection of control flow utility macros
|
||||
//! A collection of control flow utility macros
|
||||
|
||||
#[macro_export]
|
||||
/// A simple for loop to repeat a $body a number of times
|
||||
@@ -33,7 +33,7 @@ macro_rules! repeat {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 0);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_unless!(false, 1);
|
||||
/// 0
|
||||
@@ -65,7 +65,7 @@ macro_rules! return_unless {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 1);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_if!(false, 1);
|
||||
/// 0
|
||||
@@ -98,7 +98,7 @@ macro_rules! return_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 5);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for _ in 0..10 {
|
||||
/// for j in 0..20 {
|
||||
@@ -134,7 +134,7 @@ macro_rules! break_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 9);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for i in 0..10 {
|
||||
/// continue_if!(i == 5, 'one);
|
||||
|
||||
141
util/src/fd.rs
141
util/src/fd.rs
@@ -89,6 +89,30 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
||||
///
|
||||
/// Will panic if the given file descriptor is negative of or larger than
|
||||
/// the file descriptor numbers permitted by the operating system.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::io::Read;
|
||||
/// # use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
/// # use std::os::fd::IntoRawFd;
|
||||
/// # use rustix::fd::AsFd;
|
||||
/// # use rosenpass_util::fd::mask_fd;
|
||||
///
|
||||
/// // Open a temporary file
|
||||
/// let fd = tempfile::tempfile().unwrap().into_raw_fd();
|
||||
/// assert!(fd >= 0);
|
||||
///
|
||||
/// // Mask the file descriptor
|
||||
/// mask_fd(fd).unwrap();
|
||||
///
|
||||
/// // Verify the file descriptor now points to `/dev/null`
|
||||
/// // Reading from `/dev/null` always returns 0 bytes
|
||||
/// let mut replaced_file = unsafe { File::from_raw_fd(fd) };
|
||||
/// let mut buffer = [0u8; 4];
|
||||
/// let bytes_read = replaced_file.read(&mut buffer).unwrap();
|
||||
/// assert_eq!(bytes_read, 0);
|
||||
/// ```
|
||||
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
||||
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting,
|
||||
// it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd
|
||||
@@ -286,14 +310,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinguish different socket address familys; e.g. IP and unix sockets
|
||||
/// Distinguish different socket address families; e.g. IP and unix sockets
|
||||
#[cfg(target_os = "linux")]
|
||||
pub trait GetSocketDomain {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Retrieve the socket domain (address family)
|
||||
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
|
||||
/// Alias for [socket_domain]
|
||||
/// Alias for [Self::socket_domain]
|
||||
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
|
||||
self.socket_domain()
|
||||
}
|
||||
@@ -320,9 +344,67 @@ where
|
||||
pub trait GetUnixSocketType {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Check if the socket is a unix stream socket
|
||||
|
||||
/// Checks whether the socket is a Unix stream socket.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(true)` if the socket is a Unix stream socket.
|
||||
/// - `Ok(false)` if the socket is not a Unix stream socket.
|
||||
/// - `Err(Self::Error)` if there is an error while performing the check.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::UnixListener;
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().is_unix_stream_socket(), Ok(true)));
|
||||
/// ```
|
||||
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
|
||||
/// Returns Ok(()) only if the underlying socket is a unix stream socket
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::{UnixDatagram, UnixListener};
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().demand_unix_stream_socket(), Ok(())));
|
||||
/// // Error if the FD is a file
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// assert_eq!(temp_file.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Socket operation on non-socket (os error 88)"
|
||||
/// );
|
||||
/// // Error if the FD is a Unix stream with a wrong mode (e.g. Datagram)
|
||||
/// let f = {
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixDatagram::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert_eq!(f.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Expected unix socket in stream mode, but mode is SocketType(2)"
|
||||
/// );
|
||||
/// ```
|
||||
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
@@ -352,16 +434,65 @@ where
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Distinguish between different network socket protocols (e.g. tcp, udp)
|
||||
pub trait GetSocketProtocol {
|
||||
/// Retrieve the socket protocol
|
||||
/// Retrieves the socket's protocol.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Some(Protocol))`: The protocol of the socket if available.
|
||||
/// - `Ok(None)`: If the protocol information is unavailable.
|
||||
/// - `Err(rustix::io::Errno)`: If an error occurs while retrieving the protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().socket_protocol().unwrap().unwrap(), rustix::net::ipproto::UDP);
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
|
||||
/// Check if the socket is a udp socket
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(socket.as_fd().is_udp_socket().unwrap());
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert!(!socket.as_fd().is_udp_socket().unwrap());
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
|
||||
self.socket_protocol()?
|
||||
.map(|p| p == rustix::net::ipproto::UDP)
|
||||
.unwrap_or(false)
|
||||
.ok()
|
||||
}
|
||||
/// Return Ok(()) only if the socket is a udp socket
|
||||
|
||||
/// Ensures that the socket is a UDP socket, returning an error otherwise.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(())` if the socket is a UDP socket.
|
||||
/// - `Err(anyhow::Error)` if the socket is not a UDP socket or if an error occurs retrieving the socket protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(matches!(socket.as_fd().demand_udp_socket(), Ok(())));
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().demand_udp_socket().unwrap_err().to_string(),
|
||||
/// "Not a udp socket, instead socket protocol is: Protocol(6)");
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn demand_udp_socket(&self) -> anyhow::Result<()> {
|
||||
match self.socket_protocol() {
|
||||
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),
|
||||
|
||||
@@ -244,7 +244,6 @@
|
||||
use std::{borrow::Borrow, io};
|
||||
|
||||
use anyhow::ensure;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
/// Generic trait for accessing [std::io::Error::kind]
|
||||
///
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
//! This module provides utilities for decoding length-prefixed messages from I/O streams.
|
||||
//!
|
||||
//! Messages are prefixed with an unsigned 64-bit little-endian length header, followed by the
|
||||
//! message payload. The [`decoder::LengthPrefixDecoder`] is a central component here, maintaining
|
||||
//! internal buffers and state for partial reads and boundary checks.
|
||||
//!
|
||||
//! The module defines errors to handle size mismatches, I/O issues, and boundary violations
|
||||
//! that may occur during decoding.
|
||||
//!
|
||||
//! The abstractions provided in this module enable safe and convenient reading
|
||||
//! of structured data from streams, including handling unexpected EOFs and ensuring messages
|
||||
//! fit within allocated buffers.
|
||||
|
||||
use std::{borrow::BorrowMut, cmp::min, io};
|
||||
|
||||
use thiserror::Error;
|
||||
@@ -8,54 +21,79 @@ use crate::{
|
||||
result::ensure_or,
|
||||
};
|
||||
|
||||
/// Size in bytes of a message header carrying length information
|
||||
/// Size in bytes of the message header carrying length information.
|
||||
/// Currently, HEADER_SIZE is always 8 bytes and encodes a 64-bit little-endian number.
|
||||
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
|
||||
|
||||
/// Error enum representing sanity check failures when accessing buffer regions.
|
||||
///
|
||||
/// This error is triggered when internal offsets point outside allowable regions.
|
||||
#[derive(Error, Debug)]
|
||||
/// Error enum to represent various boundary sanity check failures during buffer operations
|
||||
pub enum SanityError {
|
||||
/// The given offset exceeded the read buffer bounds.
|
||||
#[error("Offset is out of read buffer bounds")]
|
||||
/// Error indicating that the given offset exceeds the bounds of the read buffer
|
||||
OutOfBufferBounds,
|
||||
|
||||
/// The given offset exceeded the message buffer bounds.
|
||||
#[error("Offset is out of message buffer bounds")]
|
||||
/// Error indicating that the given offset exceeds the bounds of the message buffer
|
||||
OutOfMessageBounds,
|
||||
}
|
||||
|
||||
/// Error indicating that the message size is larger than the available buffer space.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")]
|
||||
/// Error indicating that message exceeds available buffer space
|
||||
pub struct MessageTooLargeError {
|
||||
msg_size: usize,
|
||||
buf_size: usize,
|
||||
}
|
||||
|
||||
impl MessageTooLargeError {
|
||||
/// Creates a new MessageTooLargeError with the given message and buffer sizes
|
||||
/// Creates a new `MessageTooLargeError` with the given message and buffer sizes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::MessageTooLargeError;
|
||||
/// let err = MessageTooLargeError::new(1024, 512);
|
||||
/// assert_eq!(format!("{}", err), "Message too large (1024 bytes) for buffer (512 bytes)");
|
||||
/// ```
|
||||
pub fn new(msg_size: usize, buf_size: usize) -> Self {
|
||||
Self { msg_size, buf_size }
|
||||
}
|
||||
|
||||
/// Ensures that the message size fits within the buffer size
|
||||
/// Ensures the message fits within the given buffer.
|
||||
///
|
||||
/// Returns Ok(()) if the message fits, otherwise returns an error with size details
|
||||
/// Returns `Ok(())` if `msg_size <= buf_size`, otherwise returns a `MessageTooLargeError`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::MessageTooLargeError;
|
||||
/// let result = MessageTooLargeError::ensure(100, 200);
|
||||
/// assert!(result.is_ok());
|
||||
///
|
||||
/// let err = MessageTooLargeError::ensure(300, 200).unwrap_err();
|
||||
/// assert_eq!(format!("{}", err), "Message too large (300 bytes) for buffer (200 bytes)");
|
||||
/// ```
|
||||
pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> {
|
||||
let err = MessageTooLargeError { msg_size, buf_size };
|
||||
ensure_or(msg_size <= buf_size, err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return type for `ReadFromIo` operations, containing the number of bytes read and an optional message slice.
|
||||
#[derive(Debug)]
|
||||
/// Return type for ReadFromIo operations that contains the number of bytes read and an optional message slice
|
||||
pub struct ReadFromIoReturn<'a> {
|
||||
/// Number of bytes read from the input
|
||||
/// Number of bytes read.
|
||||
pub bytes_read: usize,
|
||||
/// Optional slice containing the complete message, if one was read
|
||||
/// The complete message slice if fully read, otherwise `None`.
|
||||
pub message: Option<&'a mut [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> ReadFromIoReturn<'a> {
|
||||
/// Creates a new ReadFromIoReturn with the given number of bytes read and optional message slice.
|
||||
/// Creates a new `ReadFromIoReturn`.
|
||||
///
|
||||
/// Generally used internally to represent partial or complete read results.
|
||||
pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self {
|
||||
Self {
|
||||
bytes_read,
|
||||
@@ -64,13 +102,17 @@ impl<'a> ReadFromIoReturn<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that may occur when reading from an I/O source.
|
||||
///
|
||||
/// This enum wraps I/O errors and message-size errors, allowing higher-level logic to determine
|
||||
/// if the error is a fundamental I/O problem or a size mismatch issue.
|
||||
#[derive(Debug, Error)]
|
||||
/// An enum representing errors that can occur during read operations from I/O
|
||||
pub enum ReadFromIoError {
|
||||
/// Error occurred while reading from the underlying I/O stream
|
||||
/// Error reading from the underlying I/O stream.
|
||||
#[error("Error reading from the underlying stream")]
|
||||
IoError(#[from] io::Error),
|
||||
/// Error occurred because message size exceeded buffer capacity
|
||||
|
||||
/// The message size exceeded the capacity of the available buffer.
|
||||
#[error("Message size out of buffer bounds")]
|
||||
MessageTooLargeError(#[from] MessageTooLargeError),
|
||||
}
|
||||
@@ -84,11 +126,31 @@ impl TryIoErrorKind for ReadFromIoError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
/// A decoder for length-prefixed messages
|
||||
/// A decoder for length-prefixed messages.
|
||||
///
|
||||
/// This struct provides functionality to decode messages that are prefixed with their length.
|
||||
/// It maintains internal state for header information, the message buffer, and current offset.
|
||||
/// This decoder reads a 64-bit little-endian length prefix followed by the message payload.
|
||||
/// It maintains state so that partial reads from a non-blocking or streaming source can
|
||||
/// accumulate until a full message is available.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::LengthPrefixDecoder;
|
||||
/// let data: Vec<u8> = {
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.extend_from_slice(&(5u64.to_le_bytes())); // message length = 5
|
||||
/// buf.extend_from_slice(b"hello");
|
||||
/// buf
|
||||
/// };
|
||||
///
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 64]);
|
||||
/// let mut cursor = Cursor::new(data);
|
||||
///
|
||||
/// let message = decoder.read_all_from_stdio(&mut cursor).expect("read failed");
|
||||
/// assert_eq!(message, b"hello");
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
|
||||
header: [u8; HEADER_SIZE],
|
||||
buf: Buf,
|
||||
@@ -96,33 +158,102 @@ pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
|
||||
}
|
||||
|
||||
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
/// Creates a new LengthPrefixDecoder with the given buffer
|
||||
/// Creates a new `LengthPrefixDecoder` with the provided buffer.
|
||||
///
|
||||
/// The provided buffer must be large enough to hold the expected maximum message size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::LengthPrefixDecoder;
|
||||
/// let decoder = LengthPrefixDecoder::new(vec![0; 1024]);
|
||||
/// assert_eq!(*decoder.bytes_read(), 0);
|
||||
/// ```
|
||||
pub fn new(buf: Buf) -> Self {
|
||||
let header = Default::default();
|
||||
let off = 0;
|
||||
Self { header, buf, off }
|
||||
}
|
||||
|
||||
/// Clears and zeroes all internal state
|
||||
/// Clears and zeroes all internal state.
|
||||
///
|
||||
/// This zeroizes the header and the buffer, as well as resets the offset to zero.
|
||||
pub fn clear(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
|
||||
/// Creates a new LengthPrefixDecoder from its component parts
|
||||
/// Creates a decoder from parts.
|
||||
///
|
||||
/// Typically used for low-level reconstruction of a decoder state.
|
||||
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
|
||||
Self { header, buf, off }
|
||||
}
|
||||
|
||||
/// Consumes the decoder and returns its component parts
|
||||
/// Consumes the decoder and returns its internal parts.
|
||||
///
|
||||
/// Returns the header, the underlying buffer, and the current offset.
|
||||
pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) {
|
||||
let Self { header, buf, off } = self;
|
||||
(header, buf, off)
|
||||
}
|
||||
|
||||
/// Reads a complete message from the given reader into the decoder.
|
||||
/// Reads a complete message from the given reader.
|
||||
///
|
||||
/// Retries on interrupts and returns the decoded message buffer on success.
|
||||
/// Returns an error if the read fails or encounters an unexpected EOF.
|
||||
/// Will retry on interrupts and fails if EOF is encountered prematurely. On success,
|
||||
/// returns a mutable slice of the fully read message.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Successful read
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::{LengthPrefixDecoder, ReadFromIoError, MessageTooLargeError};
|
||||
/// let mut data: Cursor<Vec<u8>> = {
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.extend_from_slice(&(3u64.to_le_bytes()));
|
||||
/// // The buffer can also be larger than the message size:
|
||||
/// // Here `cats` is 4 bytes and 1 byte longer than the message size defined in the header
|
||||
/// buf.extend_from_slice(b"cats");
|
||||
/// Cursor::new(buf)
|
||||
/// };
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 8]);
|
||||
/// let msg = decoder.read_all_from_stdio(&mut data).expect("read failed");
|
||||
/// assert_eq!(msg, b"cat");
|
||||
/// ```
|
||||
///
|
||||
/// ## MessageTooLargeError
|
||||
///
|
||||
/// Buffer of the `LengthPrefixDecoder` configured to be too small:
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::{LengthPrefixDecoder, ReadFromIoError, MessageTooLargeError};
|
||||
/// let mut data: Cursor<Vec<u8>> = {
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.extend_from_slice(&(7u64.to_le_bytes()));
|
||||
/// buf.extend_from_slice(b"giraffe");
|
||||
/// Cursor::new(buf)
|
||||
/// };
|
||||
/// // Buffer is too small, should be at least 7 bytes (defined in the header)
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 5]);
|
||||
/// let err = decoder.read_all_from_stdio(&mut data).expect_err("read should have failed");
|
||||
/// assert!(matches!(err, ReadFromIoError::MessageTooLargeError(_)));
|
||||
/// ```
|
||||
///
|
||||
/// ## IOError (EOF)
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::{LengthPrefixDecoder, ReadFromIoError, MessageTooLargeError};
|
||||
/// let mut data: Cursor<Vec<u8>> = {
|
||||
/// let mut buf = Vec::new();
|
||||
/// // Message size set to 10 bytes, but the message is only 7 bytes long
|
||||
/// buf.extend_from_slice(&(10u64.to_le_bytes()));
|
||||
/// buf.extend_from_slice(b"giraffe");
|
||||
/// Cursor::new(buf)
|
||||
/// };
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 10]);
|
||||
/// let err = decoder.read_all_from_stdio(&mut data).expect_err("read should have failed");
|
||||
/// assert!(matches!(err, ReadFromIoError::IoError(_)));
|
||||
/// ```
|
||||
pub fn read_all_from_stdio<R: io::Read>(
|
||||
&mut self,
|
||||
mut r: R,
|
||||
@@ -153,7 +284,19 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads from the given reader into the decoder's internal buffers
|
||||
/// Attempts to read from the given `Read` source into the decoder.
|
||||
///
|
||||
/// On success, returns how many bytes were read and a mutable slice of the complete message if fully available.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::{LengthPrefixDecoder, ReadFromIoReturn};
|
||||
/// let mut data = Cursor::new([4u64.to_le_bytes().as_slice(), b"cats"].concat());
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 8]);
|
||||
/// decoder.read_from_stdio(&mut data).expect("read failed");
|
||||
/// ```
|
||||
pub fn read_from_stdio<R: io::Read>(
|
||||
&mut self,
|
||||
mut r: R,
|
||||
@@ -179,7 +322,12 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the next buffer slice that can be written to
|
||||
/// Returns the next slice of internal buffer that needs data.
|
||||
///
|
||||
/// If the header is not yet fully read, returns the remaining part of the header buffer.
|
||||
/// Otherwise, returns the remaining part of the message buffer if the message size is known.
|
||||
///
|
||||
/// If no more data is needed (message fully read), returns `Ok(None)`.
|
||||
pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||
fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> {
|
||||
match buf.is_empty() {
|
||||
@@ -202,7 +350,9 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Advances the internal offset by the specified number of bytes
|
||||
/// Advances the internal offset by `count` bytes.
|
||||
///
|
||||
/// This checks that the offset does not exceed buffer or message limits.
|
||||
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
|
||||
let off = self.off + count;
|
||||
let msg_off = off.saturating_sub(HEADER_SIZE);
|
||||
@@ -220,7 +370,9 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures that the internal message buffer is large enough for the message size in the header
|
||||
/// Checks that the allocated message buffer is large enough for the message length.
|
||||
///
|
||||
/// If the header is not fully read, this does nothing. If it is, ensures the buffer fits the message.
|
||||
pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> {
|
||||
let buf_size = self.message_buffer().len();
|
||||
let msg_size = match self.get_header() {
|
||||
@@ -230,53 +382,64 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
MessageTooLargeError::ensure(msg_size, buf_size)
|
||||
}
|
||||
|
||||
/// Returns a reference to the header buffer
|
||||
/// Returns a reference to the header buffer.
|
||||
pub fn header_buffer(&self) -> &[u8] {
|
||||
&self.header[..]
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the header buffer
|
||||
/// Returns a mutable reference to the header buffer.
|
||||
pub fn header_buffer_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.header[..]
|
||||
}
|
||||
|
||||
/// Returns a reference to the message buffer
|
||||
/// Returns a reference to the underlying message buffer.
|
||||
pub fn message_buffer(&self) -> &[u8] {
|
||||
self.buf.borrow()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the message buffer
|
||||
/// Returns a mutable reference to the underlying message buffer.
|
||||
pub fn message_buffer_mut(&mut self) -> &mut [u8] {
|
||||
self.buf.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns the number of bytes read so far
|
||||
/// Returns a reference to the total number of bytes read so far.
|
||||
pub fn bytes_read(&self) -> &usize {
|
||||
&self.off
|
||||
}
|
||||
|
||||
/// Consumes the decoder and returns just the message buffer
|
||||
/// Consumes the decoder and returns the underlying buffer.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::decoder::{LengthPrefixDecoder, ReadFromIoReturn};
|
||||
/// let mut data = Cursor::new([4u64.to_le_bytes().as_slice(), b"cats"].concat());
|
||||
/// let mut decoder = LengthPrefixDecoder::new(vec![0; 8]);
|
||||
/// decoder.read_all_from_stdio(&mut data).expect("read failed");
|
||||
/// let buffer: Vec<u8> = decoder.into_message_buffer();
|
||||
/// assert_eq!(buffer, vec![99, 97, 116, 115, 0, 0, 0, 0]);
|
||||
/// ```
|
||||
pub fn into_message_buffer(self) -> Buf {
|
||||
let Self { buf, .. } = self;
|
||||
buf
|
||||
}
|
||||
|
||||
/// Returns the current offset into the header buffer
|
||||
/// Returns the current offset into the header buffer.
|
||||
pub fn header_buffer_offset(&self) -> usize {
|
||||
min(self.off, HEADER_SIZE)
|
||||
}
|
||||
|
||||
/// Returns the current offset into the message buffer
|
||||
/// Returns the current offset into the message buffer.
|
||||
pub fn message_buffer_offset(&self) -> usize {
|
||||
self.off.saturating_sub(HEADER_SIZE)
|
||||
}
|
||||
|
||||
/// Returns whether a complete header has been read
|
||||
/// Returns whether the header has been fully read.
|
||||
pub fn has_header(&self) -> bool {
|
||||
self.header_buffer_offset() == HEADER_SIZE
|
||||
}
|
||||
|
||||
/// Returns whether a complete message has been read
|
||||
/// Returns `true` if the entire message has been read, `false` otherwise.
|
||||
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
|
||||
self.ensure_sufficient_msg_buffer()?;
|
||||
let msg_size = match self.get_header() {
|
||||
@@ -286,55 +449,55 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
Ok(self.message_buffer_avail().len() == msg_size)
|
||||
}
|
||||
|
||||
/// Returns a slice of the available data in the header buffer
|
||||
/// Returns the currently read portion of the header.
|
||||
pub fn header_buffer_avail(&self) -> &[u8] {
|
||||
let off = self.header_buffer_offset();
|
||||
&self.header_buffer()[..off]
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the available data in the header buffer
|
||||
/// Returns a mutable slice of the currently read portion of the header.
|
||||
pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
|
||||
let off = self.header_buffer_offset();
|
||||
&mut self.header_buffer_mut()[..off]
|
||||
}
|
||||
|
||||
/// Returns a slice of the remaining space in the header buffer
|
||||
/// Returns the remaining unread portion of the header.
|
||||
pub fn header_buffer_left(&self) -> &[u8] {
|
||||
let off = self.header_buffer_offset();
|
||||
&self.header_buffer()[off..]
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the remaining space in the header buffer
|
||||
/// Returns a mutable slice of the remaining unread portion of the header.
|
||||
pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
|
||||
let off = self.header_buffer_offset();
|
||||
&mut self.header_buffer_mut()[off..]
|
||||
}
|
||||
|
||||
/// Returns a slice of the available data in the message buffer
|
||||
/// Returns the currently read portion of the message.
|
||||
pub fn message_buffer_avail(&self) -> &[u8] {
|
||||
let off = self.message_buffer_offset();
|
||||
&self.message_buffer()[..off]
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the available data in the message buffer
|
||||
/// Returns a mutable slice of the currently read portion of the message.
|
||||
pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
|
||||
let off = self.message_buffer_offset();
|
||||
&mut self.message_buffer_mut()[..off]
|
||||
}
|
||||
|
||||
/// Returns a slice of the remaining space in the message buffer
|
||||
/// Returns the remaining unread portion of the message buffer.
|
||||
pub fn message_buffer_left(&self) -> &[u8] {
|
||||
let off = self.message_buffer_offset();
|
||||
&self.message_buffer()[off..]
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the remaining space in the message buffer
|
||||
/// Returns a mutable slice of the remaining unread portion of the message buffer.
|
||||
pub fn message_buffer_left_mut(&mut self) -> &mut [u8] {
|
||||
let off = self.message_buffer_offset();
|
||||
&mut self.message_buffer_mut()[off..]
|
||||
}
|
||||
|
||||
/// Returns the message size from the header if available
|
||||
/// Returns the message size from the header if fully read.
|
||||
pub fn get_header(&self) -> Option<usize> {
|
||||
match self.header_buffer_offset() == HEADER_SIZE {
|
||||
false => None,
|
||||
@@ -342,23 +505,23 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the size of the message if header is available
|
||||
/// Returns the message size if known (i.e., if the header is fully read).
|
||||
pub fn message_size(&self) -> Option<usize> {
|
||||
self.get_header()
|
||||
}
|
||||
|
||||
/// Returns the total size of the encoded message including header
|
||||
/// Returns the total size of the encoded message (header + payload) if known.
|
||||
pub fn encoded_message_bytes(&self) -> Option<usize> {
|
||||
self.message_size().map(|sz| sz + HEADER_SIZE)
|
||||
}
|
||||
|
||||
/// Returns a slice of the message fragment if available
|
||||
/// Returns the complete message fragment if the header is known and buffer is sufficient.
|
||||
pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||
self.ensure_sufficient_msg_buffer()?;
|
||||
Ok(self.message_size().map(|sz| &self.message_buffer()[..sz]))
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the message fragment if available
|
||||
/// Returns a mutable reference to the complete message fragment.
|
||||
pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||
self.ensure_sufficient_msg_buffer()?;
|
||||
Ok(self
|
||||
@@ -366,14 +529,14 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
.map(|sz| &mut self.message_buffer_mut()[..sz]))
|
||||
}
|
||||
|
||||
/// Returns a slice of the available data in the message fragment
|
||||
/// Returns the portion of the message fragment that has been filled so far.
|
||||
pub fn message_fragment_avail(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||
let off = self.message_buffer_avail().len();
|
||||
self.message_fragment()
|
||||
.map(|frag| frag.map(|frag| &frag[..off]))
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the available data in the message fragment
|
||||
/// Returns a mutable portion of the message fragment that has been filled so far.
|
||||
pub fn message_fragment_avail_mut(
|
||||
&mut self,
|
||||
) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||
@@ -382,28 +545,32 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||
.map(|frag| frag.map(|frag| &mut frag[..off]))
|
||||
}
|
||||
|
||||
/// Returns a slice of the remaining space in the message fragment
|
||||
/// Returns the remaining portion of the message fragment that still needs to be read.
|
||||
pub fn message_fragment_left(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||
let off = self.message_buffer_avail().len();
|
||||
self.message_fragment()
|
||||
.map(|frag| frag.map(|frag| &frag[off..]))
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the remaining space in the message fragment
|
||||
/// Returns a mutable slice of the remaining portion of the message fragment that still needs to be read.
|
||||
pub fn message_fragment_left_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||
let off = self.message_buffer_avail().len();
|
||||
self.message_fragment_mut()
|
||||
.map(|frag| frag.map(|frag| &mut frag[off..]))
|
||||
}
|
||||
|
||||
/// Returns a slice of the complete message if available
|
||||
/// If the entire message is available, returns a reference to it.
|
||||
///
|
||||
/// Otherwise returns `Ok(None)`.
|
||||
pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||
let sz = self.message_size();
|
||||
self.message_fragment_avail()
|
||||
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag)))
|
||||
}
|
||||
|
||||
/// Returns a mutable slice of the complete message if available
|
||||
/// If the entire message is available, returns a mutable reference to it.
|
||||
///
|
||||
/// Otherwise returns `Ok(None)`.
|
||||
pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||
let sz = self.message_size();
|
||||
self.message_fragment_avail_mut()
|
||||
@@ -418,3 +585,107 @@ impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixDecoder<Buf> {
|
||||
self.off.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_read_from_stdio() {
|
||||
use std::io::Cursor;
|
||||
let mut data = {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&(8u64.to_le_bytes()));
|
||||
buf.extend_from_slice(b"cats"); // provide only half of the message
|
||||
Cursor::new(buf)
|
||||
};
|
||||
|
||||
let mut decoder = LengthPrefixDecoder::new(vec![0; 9]);
|
||||
|
||||
fn loop_read(decoder: &mut LengthPrefixDecoder<Vec<u8>>, data: &mut Cursor<Vec<u8>>) {
|
||||
// Read until the buffer is fully read
|
||||
let data_len = data.get_ref().len();
|
||||
loop {
|
||||
let result: ReadFromIoReturn =
|
||||
decoder.read_from_stdio(&mut *data).expect("read failed");
|
||||
if data.position() as usize == data_len {
|
||||
// the entire data was read
|
||||
break;
|
||||
}
|
||||
assert!(result.message.is_none());
|
||||
assert!(result.bytes_read > 0); // at least 1 byte was read (or all data was read)
|
||||
}
|
||||
}
|
||||
|
||||
loop_read(&mut decoder, &mut data);
|
||||
|
||||
// INSERT HERE A TEST FOR EACH INTERNAL METHOD OF LengthPrefixDecoder (decoder)
|
||||
assert_eq!(decoder.message_size(), Some(8));
|
||||
|
||||
// Header-related assertions
|
||||
assert!(decoder.has_header());
|
||||
assert_eq!(decoder.has_message().ok(), Some(false));
|
||||
assert_eq!(decoder.header_buffer_offset(), HEADER_SIZE);
|
||||
assert_eq!(decoder.header_buffer_avail().len(), HEADER_SIZE);
|
||||
assert_eq!(decoder.header_buffer_left().len(), 0);
|
||||
{
|
||||
let header_buffer_mut: &mut [u8] = decoder.header_buffer_avail_mut();
|
||||
assert_eq!(header_buffer_mut, &[8, 0, 0, 0, 0, 0, 0, 0]);
|
||||
let header_buffer_ref: &[u8] = decoder.header_buffer_avail();
|
||||
assert_eq!(header_buffer_ref, &[8, 0, 0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
assert_eq!(decoder.get_header(), Some(8));
|
||||
assert_eq!(decoder.message_size(), Some(8));
|
||||
assert_eq!(decoder.encoded_message_bytes(), Some(8 + HEADER_SIZE));
|
||||
|
||||
// Message-related assertions
|
||||
assert_eq!(*decoder.bytes_read(), 12);
|
||||
assert_eq!(decoder.message_buffer_offset(), 4); // "cats" is 4 bytes
|
||||
assert_eq!(decoder.message_buffer_avail(), b"cats");
|
||||
assert_eq!(decoder.message_buffer_avail_mut(), b"cats");
|
||||
assert_eq!(decoder.message_buffer_left().len(), 5); // buffer size is 9, 4 read -> 5 left
|
||||
assert_eq!(decoder.message_buffer_left_mut().len(), 5);
|
||||
assert!(!decoder.has_message().unwrap()); // not fully read
|
||||
|
||||
// Message fragment assertions
|
||||
let frag = decoder.message_fragment().unwrap().unwrap();
|
||||
assert_eq!(frag.len(), 8); // full message fragment slice (not fully filled)
|
||||
let frag_avail = decoder.message_fragment_avail().unwrap().unwrap();
|
||||
assert_eq!(frag_avail, b"cats"); // available portion matches what's read
|
||||
let frag_left = decoder.message_fragment_left().unwrap().unwrap();
|
||||
assert_eq!(frag_left.len(), 4); // 4 bytes remain to complete the message
|
||||
assert_eq!(decoder.message().unwrap(), None); // full message not yet available
|
||||
|
||||
// disassemble the decoder and reassemble it
|
||||
let (header, buf, off) = decoder.clone().into_parts();
|
||||
let mut decoder = LengthPrefixDecoder::from_parts(header, buf, off);
|
||||
|
||||
let mut data = Cursor::new(Vec::from(b"dogs"));
|
||||
loop_read(&mut decoder, &mut data);
|
||||
|
||||
// After providing the remaining "dogs" data, the message should now be fully available.
|
||||
assert!(decoder.has_message().unwrap());
|
||||
assert_eq!(decoder.message().unwrap().unwrap(), b"catsdogs");
|
||||
|
||||
// At this point:
|
||||
// - The entire message (8 bytes) plus the header (8 bytes for the length) should be accounted for.
|
||||
assert_eq!(
|
||||
decoder.message_fragment_avail().unwrap().unwrap(),
|
||||
b"catsdogs"
|
||||
);
|
||||
assert!(decoder.message_fragment_left().unwrap().unwrap().is_empty());
|
||||
|
||||
// The offsets and buffers should reflect that everything is read.
|
||||
assert_eq!(decoder.message_buffer_offset(), 8); // all 8 message bytes are now read
|
||||
assert_eq!(decoder.message_buffer_avail(), b"catsdogs");
|
||||
assert_eq!(decoder.message_buffer_left().len(), 1); // buffer size was 9, 8 read -> 1 left unused
|
||||
|
||||
// No more data needed to complete the message.
|
||||
assert!(decoder.next_slice_to_write_to().unwrap().is_none());
|
||||
|
||||
// clear the decoder
|
||||
decoder.clear();
|
||||
assert_eq!(decoder.buf, vec![0; 9]);
|
||||
assert_eq!(decoder.off, 0);
|
||||
assert_eq!(decoder.header, [0; HEADER_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub mod io;
|
||||
pub mod length_prefix_encoding;
|
||||
/// Memory manipulation and allocation utilities.
|
||||
pub mod mem;
|
||||
/// MIO integration utilities.
|
||||
/// [MIO (Metal I/O)](https://docs.rs/crate/mio/) integration utilities.
|
||||
pub mod mio;
|
||||
/// Extended Option type functionality.
|
||||
pub mod option;
|
||||
|
||||
166
util/src/mem.rs
166
util/src/mem.rs
@@ -1,10 +1,33 @@
|
||||
//!
|
||||
//! This module provides functions for copying data, concatenating byte arrays,
|
||||
//! and various traits and types that help manage values, including preventing
|
||||
//! drops, discarding results, and swapping values.
|
||||
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::cmp::min;
|
||||
use std::mem::{forget, swap};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Concatenate two byte arrays
|
||||
// TODO: Zeroize result?
|
||||
/// Concatenate multiple byte slices into a fixed-size byte array.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the concatenated length does not match the declared length.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::cat;
|
||||
/// let arr = cat!(6; b"abc", b"def");
|
||||
/// assert_eq!(&arr, b"abcdef");
|
||||
///
|
||||
/// let err = std::panic::catch_unwind(|| cat!(5; b"abc", b"def"));
|
||||
/// assert!(matches!(err, Err(_)));
|
||||
///
|
||||
/// let err = std::panic::catch_unwind(|| cat!(7; b"abc", b"def"));
|
||||
/// assert!(matches!(err, Err(_)));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! cat {
|
||||
($len:expr; $($toks:expr),+) => {{
|
||||
@@ -22,12 +45,37 @@ macro_rules! cat {
|
||||
}
|
||||
|
||||
// TODO: consistent inout ordering
|
||||
/// Copy all bytes from `src` to `dst`. The lengths must match.
|
||||
/// Copy bytes from `src` to `dst`, requiring equal lengths.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if lengths differ.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::cpy;
|
||||
/// let src = [1, 2, 3];
|
||||
/// let mut dst = [0; 3];
|
||||
/// cpy(&src, &mut dst);
|
||||
/// assert_eq!(dst, [1, 2, 3]);
|
||||
/// ```
|
||||
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
dst.borrow_mut().copy_from_slice(src.borrow());
|
||||
}
|
||||
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length,
|
||||
/// copy as many bytes as possible.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::cpy_min;
|
||||
/// let src = [1, 2, 3, 4];
|
||||
/// let mut dst = [0; 2];
|
||||
/// cpy_min(&src, &mut dst);
|
||||
/// assert_eq!(dst, [1, 2]);
|
||||
/// ```
|
||||
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
let src = src.borrow();
|
||||
let dst = dst.borrow_mut();
|
||||
@@ -35,20 +83,30 @@ pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d
|
||||
dst[..len].copy_from_slice(&src[..len]);
|
||||
}
|
||||
|
||||
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed
|
||||
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying
|
||||
/// variable is freed
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::Forgetting;
|
||||
/// let f = Forgetting::new(String::from("hello"));
|
||||
/// assert_eq!(&*f, "hello");
|
||||
/// let val = f.extract();
|
||||
/// assert_eq!(val, "hello");
|
||||
/// ```
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
|
||||
pub struct Forgetting<T> {
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Forgetting<T> {
|
||||
/// Creates a new `Forgetting<T>` instance containing the given value.
|
||||
/// Create a new `Forgetting` wrapping `value`.
|
||||
pub fn new(value: T) -> Self {
|
||||
let value = Some(value);
|
||||
Self { value }
|
||||
Self { value: Some(value) }
|
||||
}
|
||||
|
||||
/// Extracts and returns the contained value, consuming self.
|
||||
/// Consume and return the inner value.
|
||||
pub fn extract(mut self) -> T {
|
||||
let mut value = None;
|
||||
swap(&mut value, &mut self.value);
|
||||
@@ -97,6 +155,13 @@ impl<T> Drop for Forgetting<T> {
|
||||
}
|
||||
|
||||
/// A trait that provides a method to discard a value without explicitly handling its results.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::DiscardResultExt;
|
||||
/// let result: () = (|| { return 42 })().discard_result(); // Just discard
|
||||
/// ```
|
||||
pub trait DiscardResultExt {
|
||||
/// Consumes and discards a value without doing anything with it.
|
||||
fn discard_result(self);
|
||||
@@ -107,8 +172,16 @@ impl<T> DiscardResultExt for T {
|
||||
}
|
||||
|
||||
/// Trait that provides a method to explicitly forget values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::ForgetExt;
|
||||
/// let s = String::from("no drop");
|
||||
/// s.forget(); // destructor not run
|
||||
/// ```
|
||||
pub trait ForgetExt {
|
||||
/// Consumes and forgets a value, preventing its destructor from running.
|
||||
/// Forget the value.
|
||||
fn forget(self);
|
||||
}
|
||||
|
||||
@@ -119,10 +192,23 @@ impl<T> ForgetExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides methods for swapping values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::mem::SwapWithExt;
|
||||
/// let mut x = 10;
|
||||
/// let mut y = x.swap_with(20);
|
||||
/// assert_eq!(x, 20);
|
||||
/// assert_eq!(y, 10);
|
||||
/// y.swap_with_mut(&mut x);
|
||||
/// assert_eq!(x, 10);
|
||||
/// assert_eq!(y, 20);
|
||||
/// ```
|
||||
pub trait SwapWithExt {
|
||||
/// Takes ownership of `other` and swaps its value with `self`, returning the original value.
|
||||
/// Swap values and return the old value of `self`.
|
||||
fn swap_with(&mut self, other: Self) -> Self;
|
||||
/// Swaps the values between `self` and `other` in place.
|
||||
/// Swap values in place with another mutable reference.
|
||||
fn swap_with_mut(&mut self, other: &mut Self);
|
||||
}
|
||||
|
||||
@@ -138,8 +224,18 @@ impl<T> SwapWithExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides methods for swapping values with default values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::mem::SwapWithDefaultExt;
|
||||
/// let mut s = String::from("abc");
|
||||
/// let old = s.swap_with_default();
|
||||
/// assert_eq!(old, "abc");
|
||||
/// assert_eq!(s, "");
|
||||
/// ```
|
||||
pub trait SwapWithDefaultExt {
|
||||
/// Takes the current value and replaces it with the default value, returning the original.
|
||||
/// Swap with `Self::default()`.
|
||||
fn swap_with_default(&mut self) -> Self;
|
||||
}
|
||||
|
||||
@@ -150,6 +246,26 @@ impl<T: Default> SwapWithDefaultExt for T {
|
||||
}
|
||||
|
||||
/// Extension trait that provides a method to explicitly move values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::rc::Rc;
|
||||
/// use rosenpass_util::mem::MoveExt;
|
||||
/// let val = 42;
|
||||
/// let another_val = val.move_here();
|
||||
/// assert_eq!(another_val, 42);
|
||||
/// // val is now inaccessible
|
||||
///
|
||||
/// let value = Rc::new(42);
|
||||
/// let clone = Rc::clone(&value);
|
||||
///
|
||||
/// assert_eq!(Rc::strong_count(&value), 2);
|
||||
///
|
||||
/// clone.move_here(); // this will drop the second reference
|
||||
///
|
||||
/// assert_eq!(Rc::strong_count(&value), 1);
|
||||
/// ```
|
||||
pub trait MoveExt {
|
||||
/// Deliberately move the value
|
||||
///
|
||||
@@ -163,3 +279,29 @@ impl<T: Sized> MoveExt for T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_forgetting {
|
||||
use crate::mem::Forgetting;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_forgetting() {
|
||||
let drop_was_called = Arc::new(AtomicBool::new(false));
|
||||
struct SetFlagOnDrop(Arc<AtomicBool>);
|
||||
impl Drop for SetFlagOnDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
}
|
||||
drop(SetFlagOnDrop(drop_was_called.clone()));
|
||||
assert!(drop_was_called.load(SeqCst));
|
||||
// reset flag and use Forgetting
|
||||
drop_was_called.store(false, SeqCst);
|
||||
let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone()));
|
||||
drop(forgetting);
|
||||
assert_eq!(drop_was_called.load(SeqCst), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
result::OkExt,
|
||||
};
|
||||
|
||||
/// Module containing I/O interest flags for Unix operations
|
||||
/// Module containing I/O interest flags for Unix operations (see also: [mio::Interest])
|
||||
pub mod interest {
|
||||
use mio::Interest;
|
||||
|
||||
@@ -20,9 +20,48 @@ pub mod interest {
|
||||
pub const RW: Interest = R.add(W);
|
||||
}
|
||||
|
||||
/// Extension trait providing additional functionality for Unix listener
|
||||
/// Extension trait providing additional functionality for a Unix listener
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use mio::net::{UnixListener, UnixStream};
|
||||
/// use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt};
|
||||
///
|
||||
/// use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// // This would be the UDS created by an external source
|
||||
/// let socket_path = "/tmp/rp_mio_uds_test_socket";
|
||||
/// if Path::new(socket_path).exists() {
|
||||
/// std::fs::remove_file(socket_path).expect("Failed to remove existing socket");
|
||||
/// }
|
||||
///
|
||||
/// // An extended MIO listener can then be created by claiming the existing socket
|
||||
/// // Note that the original descriptor is not reused, but copied before claiming it here
|
||||
/// let listener = UnixListener::bind(socket_path).unwrap();
|
||||
/// let listener_fd: RawFd = listener.as_raw_fd();
|
||||
/// let ext_listener = <UnixListener as UnixListenerExt>
|
||||
/// ::claim_fd(listener_fd).expect("Failed to claim_fd for ext_listener socket");
|
||||
///
|
||||
/// // Similarly, "client" connections can be established by claiming existing sockets
|
||||
/// // Note that in this case, the file descriptor will be reused (safety implications!)
|
||||
/// let stream = UnixStream::connect(socket_path).unwrap();
|
||||
/// let stream_fd = stream.into_raw_fd();
|
||||
/// let ext_stream = <UnixStream as UnixStreamExt>
|
||||
/// ::claim_fd_inplace(stream_fd).expect("Failed to claim_fd_inplace for ext_stream socket");
|
||||
///
|
||||
/// // Handle accepted connections...
|
||||
/// ext_listener.accept().expect("Failed to accept incoming connection");
|
||||
///
|
||||
/// // Send or receive messages ...
|
||||
///
|
||||
/// // Cleanup, shutdown etc. goes here ...
|
||||
/// std::fs::remove_file(socket_path).unwrap();
|
||||
/// ```
|
||||
pub trait UnixListenerExt: Sized {
|
||||
/// Creates a new Unix listener by claiming ownership of a raw file descriptor
|
||||
/// (see [fd::claim_fd](crate::fd::claim_fd))
|
||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
@@ -36,15 +75,17 @@ impl UnixListenerExt for UnixListener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait providing additional functionality for Unix streams
|
||||
/// Extension trait providing additional functionality for a Unix stream
|
||||
pub trait UnixStreamExt: Sized {
|
||||
/// Creates a new Unix stream from an owned file descriptor
|
||||
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
|
||||
|
||||
/// Claims ownership of a raw file descriptor and creates a new Unix stream
|
||||
/// (see [fd::claim_fd](crate::fd::claim_fd))
|
||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||
|
||||
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
|
||||
/// (see [fd::claim_fd_inplace](crate::fd::claim_fd_inplace))
|
||||
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,58 @@ use crate::fd::{claim_fd_inplace, IntoStdioErr};
|
||||
/// A wrapper around a socket that combines reading from the socket with tracking
|
||||
/// received file descriptors. Limits the maximum number of file descriptors that
|
||||
/// can be received in a single read operation via the `MAX_FDS` parameter.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::collections::VecDeque;
|
||||
/// use std::io::Cursor;
|
||||
/// use std::io::Read;
|
||||
/// use std::os::fd::AsRawFd;
|
||||
/// use std::os::fd::OwnedFd;
|
||||
///
|
||||
/// use mio::net::UnixStream;
|
||||
/// use rosenpass_util::mio::ReadWithFileDescriptors;
|
||||
/// use rosenpass_util::io::TryIoResultKindHintExt;
|
||||
///
|
||||
/// const MAX_REQUEST_FDS : usize = 2; // Limit to 2 descriptors per read operation
|
||||
/// let mut read_fd_buffer = VecDeque::<OwnedFd>::new(); // File descriptor queue
|
||||
///
|
||||
/// // In this case, the unused writable end of the connection can be ignored
|
||||
/// let (io_stream, _) = UnixStream::pair().expect("failed to create socket pair");
|
||||
///
|
||||
/// // Wait until the output stream is writable...
|
||||
///
|
||||
/// // Wrap the socket to start tracking received file descriptors
|
||||
/// let mut fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
|
||||
/// &io_stream,
|
||||
/// &mut read_fd_buffer,
|
||||
/// );
|
||||
////
|
||||
/// // Simulated reads; the actual operations will depend on the protocol (implementation details)
|
||||
/// let mut recv_buffer = Vec::<u8>::new();
|
||||
/// let bytes_read = fd_passing_sock.read(&mut recv_buffer[..]).expect("error reading from socket");
|
||||
/// assert_eq!(bytes_read, 0);
|
||||
/// assert_eq!(&recv_buffer[..bytes_read], []);
|
||||
///
|
||||
/// // Alternatively, it's possible to use the try_io_err_kind_hint utility provided by this crate
|
||||
/// match fd_passing_sock.read(&mut recv_buffer).try_io_err_kind_hint() {
|
||||
/// Err(_) => {
|
||||
/// // Handle errors here ...
|
||||
/// }
|
||||
/// Ok(result) => {
|
||||
/// // Process messages here ...
|
||||
/// assert_eq!(0, result); // Nothing to read in this example
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// // The wrapped components can still be accessed
|
||||
/// assert_eq!(fd_passing_sock.socket().as_raw_fd(), io_stream.as_raw_fd());
|
||||
/// let (socket, fd_queue) = fd_passing_sock.into_parts();
|
||||
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
|
||||
///
|
||||
/// // Shutdown, cleanup, etc. goes here ...
|
||||
/// ```
|
||||
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
|
||||
where
|
||||
Sock: FdPassingExt,
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
/// Try block basically…returns a result and allows the use of the question mark operator inside
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use anyhow::Result;
|
||||
/// # use rosenpass_util::attempt;
|
||||
/// let result: Result<i32> = attempt!({
|
||||
/// let x = 42;
|
||||
/// Ok(x)
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(result.unwrap(), 42);
|
||||
///
|
||||
/// let error_result: Result<()> = attempt!({
|
||||
/// Err(anyhow::anyhow!("some error"))
|
||||
/// });
|
||||
///
|
||||
/// assert!(error_result.is_err());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! attempt {
|
||||
($block:expr) => {
|
||||
@@ -9,6 +27,19 @@ macro_rules! attempt {
|
||||
}
|
||||
|
||||
/// Trait for the ok operation, which provides a way to convert a value into a Result
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use rosenpass_util::result::OkExt;
|
||||
/// let value: i32 = 42;
|
||||
/// let result: Result<i32, &str> = value.ok();
|
||||
///
|
||||
/// assert_eq!(result, Ok(42));
|
||||
///
|
||||
/// let value = "hello";
|
||||
/// let result: Result<&str, &str> = value.ok();
|
||||
///
|
||||
/// assert_eq!(result, Ok("hello"));
|
||||
/// ```
|
||||
pub trait OkExt<E>: Sized {
|
||||
/// Wraps a value in a Result::Ok variant
|
||||
fn ok(self) -> Result<Self, E>;
|
||||
@@ -26,6 +57,11 @@ impl<T, E> OkExt<E> for T {
|
||||
/// the function will not panic.
|
||||
///
|
||||
/// Implementations must not panic.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use rosenpass_util::result::GuaranteedValue;
|
||||
/// let x:u32 = 10u8.try_into().guaranteed();
|
||||
/// ```
|
||||
pub trait GuaranteedValue {
|
||||
/// The value type that will be returned by guaranteed()
|
||||
type Value;
|
||||
|
||||
@@ -3,7 +3,23 @@ use typenum::int::{NInt, PInt, Z0};
|
||||
use typenum::marker_traits as markers;
|
||||
use typenum::uint::{UInt, UTerm};
|
||||
|
||||
/// Convenience macro to convert type numbers to constant integers
|
||||
/// Convenience macro to convert [`typenum`] type numbers to constant integers.
|
||||
///
|
||||
/// This macro takes a [`typenum`] type-level integer (like `U5`, `P3`, or `N7`)
|
||||
/// and converts it into its equivalent constant integer value at compile time.
|
||||
/// By default, it converts to a suitable unsigned integer type, but you can
|
||||
/// specify a target type explicitly using `typenum2const!(Type as i32)`,
|
||||
/// for example.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use typenum::consts::U10;
|
||||
/// # use rosenpass_util::typenum2const;
|
||||
///
|
||||
/// const TEN: u32 = typenum2const!(U10 as u32);
|
||||
/// assert_eq!(TEN, 10);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! typenum2const {
|
||||
($val:ty) => {
|
||||
@@ -14,35 +30,80 @@ macro_rules! typenum2const {
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait implemented by constant integers to facilitate conversion to constant integers
|
||||
/// A trait implemented by type-level integers to facilitate their conversion
|
||||
/// into constant values.
|
||||
///
|
||||
/// Types from the [`typenum`] crate (like `U5`, `P3`, or `N7`) can implement
|
||||
/// `IntoConst` to produce a compile-time constant integer of the specified
|
||||
/// type. This trait is part of the underlying mechanism used by the
|
||||
/// [`crate::typenum2const`] macro.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::typenum2const;
|
||||
/// use typenum::consts::U42;
|
||||
/// use rosenpass_util::typenum::IntoConst;
|
||||
///
|
||||
/// // Directly using IntoConst:
|
||||
/// const VALUE: u64 = <U42 as IntoConst<u64>>::VALUE;
|
||||
/// assert_eq!(VALUE, 42);
|
||||
///
|
||||
/// // Or via the macro:
|
||||
/// const VALUE_MACRO: u64 = typenum2const!(U42 as u64);
|
||||
/// assert_eq!(VALUE_MACRO, 42);
|
||||
/// ```
|
||||
pub trait IntoConst<T> {
|
||||
/// The constant value after conversion
|
||||
/// The constant value after conversion.
|
||||
const VALUE: T;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Internal struct for applying a negative sign to an unsigned type-level integer during conversion.
|
||||
///
|
||||
/// This is part of the implementation detail for signed conversions. It uses
|
||||
/// [`AssociatedUnsigned`] to determine the underlying unsigned type and negates its value.
|
||||
struct ConstApplyNegSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
|
||||
*const T,
|
||||
*const Param,
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Internal struct for applying a positive sign to an unsigned type-level integer during conversion.
|
||||
///
|
||||
/// This is used as part of converting a positive signed type-level integer to its runtime integer
|
||||
/// value, ensuring that the correct unsigned representation is known via [`AssociatedUnsigned`].
|
||||
struct ConstApplyPosSign<T: AssociatedUnsigned, Param: IntoConst<<T as AssociatedUnsigned>::Type>>(
|
||||
*const T,
|
||||
*const Param,
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
|
||||
/// Internal struct representing a left-shift operation on a type-level integer.
|
||||
///
|
||||
/// Used as part of compile-time computations. `SHIFT` determines how many bits the value will be
|
||||
/// shifted to the left.
|
||||
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param);
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
|
||||
/// Internal struct representing an addition operation between two type-level integers.
|
||||
///
|
||||
/// `ConstAdd` is another building block for compile-time arithmetic on type-level integers before
|
||||
/// their conversion to runtime constants.
|
||||
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs);
|
||||
|
||||
/// Assigns an unsigned type to a signed type
|
||||
/// Associates an unsigned type with a signed type, enabling conversions between signed and unsigned
|
||||
/// representations of compile-time integers.
|
||||
///
|
||||
/// This trait is used internally to facilitate the conversion of signed [`typenum`] integers by
|
||||
/// referencing their underlying unsigned representation.
|
||||
trait AssociatedUnsigned {
|
||||
/// The associated unsigned type.
|
||||
type Type;
|
||||
}
|
||||
|
||||
/// Internal macro implementing the [`IntoConst`] trait for a given mapping from a type-level integer
|
||||
/// to a concrete integer type.
|
||||
macro_rules! impl_into_const {
|
||||
( $from:ty as $to:ty := $impl:expr) => {
|
||||
impl IntoConst<$to> for $from {
|
||||
@@ -51,6 +112,10 @@ macro_rules! impl_into_const {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing common `IntoConst` logic for various numeric types.
|
||||
///
|
||||
/// It sets up `Z0`, `B0`, `B1`, `UTerm`, and also provides default implementations for
|
||||
/// `ConstLshift` and `ConstAdd`.
|
||||
macro_rules! impl_numeric_into_const_common {
|
||||
($type:ty) => {
|
||||
impl_into_const! { Z0 as $type := 0 }
|
||||
@@ -73,6 +138,10 @@ macro_rules! impl_numeric_into_const_common {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing `IntoConst` for unsigned integer types.
|
||||
///
|
||||
/// It sets up conversions for multiple unsigned integer target types and
|
||||
/// provides the positive sign application implementation.
|
||||
macro_rules! impl_numeric_into_const_unsigned {
|
||||
($($to_list:ty),*) => {
|
||||
$( impl_numeric_into_const_unsigned! { @impl $to_list } )*
|
||||
@@ -91,6 +160,9 @@ macro_rules! impl_numeric_into_const_unsigned {
|
||||
};
|
||||
}
|
||||
|
||||
/// Internal macro implementing `IntoConst` for signed integer types.
|
||||
///
|
||||
/// It uses their associated unsigned types to handle positive and negative conversions correctly.
|
||||
macro_rules! impl_numeric_into_const_signed {
|
||||
($($to_list:ty : $unsigned_list:ty),*) => {
|
||||
$( impl_numeric_into_const_signed! { @impl $to_list : $unsigned_list} )*
|
||||
@@ -110,9 +182,8 @@ macro_rules! impl_numeric_into_const_signed {
|
||||
impl<Param: IntoConst<$unsigned>> IntoConst<$type> for ConstApplyNegSign<$type, Param> {
|
||||
const VALUE : $type =
|
||||
if Param::VALUE == (1 as $unsigned).rotate_right(1) {
|
||||
// Handle the negative value without an associated positive value (e.g. -128
|
||||
// for i8)
|
||||
< $type >::MIN
|
||||
// Handling negative values at boundaries, such as i8::MIN
|
||||
<$type>::MIN
|
||||
} else {
|
||||
-(Param::VALUE as $type)
|
||||
};
|
||||
@@ -122,10 +193,10 @@ macro_rules! impl_numeric_into_const_signed {
|
||||
|
||||
impl_into_const! { B0 as bool := false }
|
||||
impl_into_const! { B1 as bool := true }
|
||||
|
||||
impl_numeric_into_const_unsigned! { usize, u8, u16, u32, u64, u128 }
|
||||
impl_numeric_into_const_signed! { isize : usize, i8 : u8, i16 : u16, i32 : u32, i64 : u64, i128 : u128 }
|
||||
|
||||
// Unsigned type numbers to const integers
|
||||
impl<Ret, Rest, Bit> IntoConst<Ret> for UInt<Rest, Bit>
|
||||
where
|
||||
Rest: IntoConst<Ret>,
|
||||
@@ -133,26 +204,28 @@ where
|
||||
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
|
||||
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts an unsigned [`UInt`] typenum into its corresponding constant integer by
|
||||
/// decomposing it into shifts and additions on its subparts.
|
||||
const VALUE: Ret = <ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
// Signed type numbers with positive sign to const integers
|
||||
impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
|
||||
where
|
||||
Ret: AssociatedUnsigned,
|
||||
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
|
||||
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts a positive signed [`PInt`] typenum into its corresponding constant integer.
|
||||
const VALUE: Ret = <ConstApplyPosSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
// Signed type numbers with negative sign to const integers
|
||||
impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
|
||||
where
|
||||
Ret: AssociatedUnsigned,
|
||||
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
|
||||
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>,
|
||||
{
|
||||
/// Converts a negative signed [`NInt`] typenum into its corresponding constant integer.
|
||||
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
//! This module provides utilities for working with zero-copy references
|
||||
//! and slices.
|
||||
//!
|
||||
//! It offers the following primary abstractions and traits:
|
||||
//!
|
||||
//! - [`RefMaker`](crate::zerocopy::RefMaker): A helper structure for safely
|
||||
//! creating `zerocopy::Ref` references from byte slices.
|
||||
//! - [`ZerocopyEmancipateExt`](crate::zerocopy::ZerocopyEmancipateExt):
|
||||
//! A trait to convert `Ref<B, T>` into a borrowed `Ref<&[u8], T>`.
|
||||
//! - [`ZerocopyEmancipateMutExt`](crate::zerocopy::ZerocopyEmancipateMutExt):
|
||||
//! A trait to convert `Ref<B, T>` into a borrowed mutable `Ref<&mut [u8], T>`.
|
||||
//! - [`ZerocopySliceExt`](crate::zerocopy::ZerocopySliceExt): Extension methods
|
||||
//! for parsing byte slices into zero-copy references.
|
||||
//! - [`ZerocopyMutSliceExt`](crate::zerocopy::ZerocopyMutSliceExt):
|
||||
//! Extension methods for parsing and zeroizing byte slices into zero-copy
|
||||
//! references.
|
||||
|
||||
mod ref_maker;
|
||||
mod zerocopy_ref_ext;
|
||||
mod zerocopy_slice_ext;
|
||||
|
||||
@@ -1,74 +1,206 @@
|
||||
use std::marker::PhantomData;
|
||||
//! A module providing the [`RefMaker`] type and its associated methods for constructing
|
||||
//! [`zerocopy::Ref`] references from byte buffers.
|
||||
|
||||
use anyhow::{ensure, Context};
|
||||
use std::marker::PhantomData;
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::zeroize::ZeroizedExt;
|
||||
|
||||
/// A convenience type for working with buffers and extracting [`zerocopy::Ref`]
|
||||
/// references.
|
||||
///
|
||||
/// `RefMaker` holds a buffer and a target type parameter `T`. Using `RefMaker`,
|
||||
/// you can validate that the provided buffer is large enough for `T` and then
|
||||
/// parse out a strongly-typed reference (`Ref`) to that data. It also provides
|
||||
/// methods for extracting prefixes and suffixes from the buffer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};///
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
///
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Header {
|
||||
/// field1: u32,
|
||||
/// field2: u16,
|
||||
/// field3: u16,
|
||||
/// }
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 8]);
|
||||
/// let bytes = AlignedBuf([0xAA, 0xBB, 0xCC, 0xDD,
|
||||
/// 0x00, 0x10, 0x20, 0x30]);
|
||||
/// let rm = RefMaker::<&[u8], Header>::new(&bytes.0);
|
||||
/// let header_ref: Ref<&[u8], Header> = rm.parse().unwrap();
|
||||
/// assert_eq!(header_ref.field1, 0xDDCCBBAA);
|
||||
/// assert_eq!(header_ref.field2, 0x1000);
|
||||
/// assert_eq!(header_ref.field3, 0x3020);
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// A convenience type for working with mutable references to a buffer and an
|
||||
/// expected target type.
|
||||
pub struct RefMaker<B: Sized, T> {
|
||||
buf: B,
|
||||
_phantom_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<B, T> RefMaker<B, T> {
|
||||
/// Creates a new RefMaker with the given buffer
|
||||
/// Creates a new `RefMaker` with the given buffer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let buffer = [0u8; 10];
|
||||
/// let rm: RefMaker<_, u32> = RefMaker::new(buffer);
|
||||
/// ```
|
||||
pub fn new(buf: B) -> Self {
|
||||
let _phantom_t = PhantomData;
|
||||
Self { buf, _phantom_t }
|
||||
}
|
||||
|
||||
/// Returns the size in bytes needed for target type T
|
||||
/// Returns the size in bytes required by the target type `T`.
|
||||
/// This is currently defined as [`std::mem::size_of::<T>`] of `T`.
|
||||
pub const fn target_size() -> usize {
|
||||
std::mem::size_of::<T>()
|
||||
}
|
||||
|
||||
/// Consumes this RefMaker and returns the inner buffer
|
||||
/// Consumes this `RefMaker` and returns the inner buffer.
|
||||
pub fn into_buf(self) -> B {
|
||||
self.buf
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner buffer
|
||||
/// Returns a reference to the inner buffer.
|
||||
pub fn buf(&self) -> &B {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner buffer
|
||||
/// Returns a mutable reference to the inner buffer.
|
||||
pub fn buf_mut(&mut self) -> &mut B {
|
||||
&mut self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ByteSlice, T> RefMaker<B, T> {
|
||||
/// Parses the buffer into a reference of type T
|
||||
/// Parses the buffer into a [`zerocopy::Ref<B, T>`].
|
||||
///
|
||||
/// This will fail if the buffer is smaller than `size_of::<T>`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized or if parsing fails.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
///
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes, Debug)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data(u32);
|
||||
///
|
||||
/// let bytes: &[u8] = &[0x01, 0x00, 0x00, 0x00];
|
||||
/// let data_ref: Ref<&[u8], Data> = RefMaker::<_, Data>::new(bytes).parse().unwrap();
|
||||
/// assert_eq!(data_ref.0, 1);
|
||||
///
|
||||
/// // errors if buffer is undersized
|
||||
/// let bytes: &[u8] = &[0x01, 0x02, 0x03];
|
||||
/// let parse_error = RefMaker::<_, Data>::new(bytes).parse()
|
||||
/// .expect_err("Should error");
|
||||
/// assert_eq!(parse_error.to_string(),
|
||||
/// "Buffer is undersized at 3 bytes (need 4 bytes)!");
|
||||
///
|
||||
/// // errors if the byte buffer is misaligned
|
||||
/// let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8];
|
||||
/// let parse_error = RefMaker::<_, Data>::new(&bytes[1..5]).parse()
|
||||
/// .expect_err("Should error");
|
||||
/// assert_eq!(parse_error.to_string(), "Parser error!");
|
||||
/// ```
|
||||
pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
|
||||
self.ensure_fit()?;
|
||||
Ref::<B, T>::new(self.buf).context("Parser error!")
|
||||
}
|
||||
|
||||
/// Splits the buffer into a RefMaker containing the first `target_size` bytes and the remaining tail
|
||||
/// Splits the internal buffer into a `RefMaker` containing a buffer with
|
||||
/// exactly `size_of::<T>()` bytes and the remaining tail of the previous
|
||||
/// internal buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8];
|
||||
/// let (prefix_rm, tail) = RefMaker::<_, u32>::new(bytes).from_prefix_with_tail().unwrap();
|
||||
/// assert_eq!(prefix_rm.bytes(), &[1,2,3,4]);
|
||||
/// assert_eq!(tail, &[5,6,7,8]);
|
||||
/// ```
|
||||
pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> {
|
||||
self.ensure_fit()?;
|
||||
let (head, tail) = self.buf.split_at(Self::target_size());
|
||||
Ok((Self::new(head), tail))
|
||||
}
|
||||
|
||||
/// Splits the buffer into two RefMakers, with the first containing the first `target_size` bytes
|
||||
/// Splits the buffer into two `RefMaker`s, with the first containing the
|
||||
/// first `size_of::<T>()` bytes and the second containing the remaining
|
||||
/// tail buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let (prefix_rm, tail) = RefMaker::<_, u32>::new(bytes).split_prefix().unwrap();
|
||||
/// assert_eq!(prefix_rm.bytes(), &[1,2,3,4]);
|
||||
/// assert_eq!(tail.bytes(), &[5,6,7,8,9,10]);
|
||||
/// ```
|
||||
pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> {
|
||||
self.ensure_fit()?;
|
||||
let (head, tail) = self.buf.split_at(Self::target_size());
|
||||
Ok((Self::new(head), Self::new(tail)))
|
||||
}
|
||||
|
||||
/// Returns a RefMaker containing only the first `target_size` bytes
|
||||
/// Returns a `RefMaker` containing only the first `size_of::<T>()` bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let prefix_rm = RefMaker::<_, u32>::new(bytes).from_prefix().unwrap();
|
||||
/// assert_eq!(prefix_rm.bytes(), &[1,2,3,4]);
|
||||
/// ```
|
||||
pub fn from_prefix(self) -> anyhow::Result<Self> {
|
||||
Ok(Self::from_prefix_with_tail(self)?.0)
|
||||
}
|
||||
|
||||
/// Splits the buffer into a RefMaker containing the last `target_size` bytes and the preceding head
|
||||
/// Splits the buffer into a `RefMaker` containing the last `size_of::<T>()`
|
||||
/// bytes as [RefMaker] and the preceding bytes as a buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let (suffix_rm, head) = RefMaker::<_, u32>::new(bytes).from_suffix_with_head().unwrap();
|
||||
/// assert_eq!(suffix_rm.bytes(), &[7,8,9,10]);
|
||||
/// assert_eq!(head, &[1,2,3,4,5,6]);
|
||||
/// ```
|
||||
pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.bytes().len() - Self::target_size();
|
||||
@@ -76,7 +208,22 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
||||
Ok((Self::new(tail), head))
|
||||
}
|
||||
|
||||
/// Splits the buffer into two RefMakers, with the second containing the last `target_size` bytes
|
||||
/// Splits the buffer into two `RefMaker`s, with the second containing the
|
||||
/// last `size_of::<T>()` bytes, and the first containing the remaining
|
||||
/// preceding bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let (head, tail) = RefMaker::<_, u32>::new(bytes).split_suffix().unwrap();
|
||||
/// assert_eq!(head.bytes(), &[1,2,3,4,5,6]);
|
||||
/// assert_eq!(tail.bytes(), &[7,8,9,10]);
|
||||
/// ```
|
||||
pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
|
||||
self.ensure_fit()?;
|
||||
let point = self.bytes().len() - Self::target_size();
|
||||
@@ -84,17 +231,46 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
||||
Ok((Self::new(head), Self::new(tail)))
|
||||
}
|
||||
|
||||
/// Returns a RefMaker containing only the last `target_size` bytes
|
||||
/// Returns a `RefMaker` containing only the last `target_size()` bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let suffix_rm = RefMaker::<_, u32>::new(bytes).from_suffix().unwrap();
|
||||
/// assert_eq!(suffix_rm.bytes(), &[7,8,9,10]);
|
||||
/// ```
|
||||
pub fn from_suffix(self) -> anyhow::Result<Self> {
|
||||
Ok(Self::from_suffix_with_head(self)?.0)
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying bytes
|
||||
/// Returns a reference to the underlying bytes.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
self.buf().deref()
|
||||
}
|
||||
|
||||
/// Ensures the buffer is large enough to hold type T
|
||||
/// Ensures that the buffer is large enough to hold a `T`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// let bytes: &[u8] = &[1,2,3,4,5,6,7,8,9,10];
|
||||
/// let rm = RefMaker::<_, u32>::new(bytes);
|
||||
/// rm.ensure_fit().unwrap();
|
||||
///
|
||||
/// let bytes: &[u8] = &[1,2,3];
|
||||
/// let rm = RefMaker::<_, u32>::new(bytes);
|
||||
/// assert!(rm.ensure_fit().is_err());
|
||||
/// ```
|
||||
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||
let have = self.bytes().len();
|
||||
let need = Self::target_size();
|
||||
@@ -107,12 +283,30 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
||||
}
|
||||
|
||||
impl<B: ByteSliceMut, T> RefMaker<B, T> {
|
||||
/// Creates a zeroed reference of type T from the buffer
|
||||
/// Creates a zeroized reference of type `T` from the buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the buffer is undersized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref}; ///
|
||||
/// # use rosenpass_util::zerocopy::RefMaker;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data([u8; 4]);
|
||||
///
|
||||
/// let mut bytes = [0xFF; 4];
|
||||
/// let data_ref: Ref<&mut [u8], Data> = RefMaker::<_, Data>::new(&mut bytes[..]).make_zeroized().unwrap();
|
||||
/// assert_eq!(data_ref.0, [0,0,0,0]);
|
||||
/// ```
|
||||
pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> {
|
||||
self.zeroized().parse()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying bytes
|
||||
/// Returns a mutable reference to the underlying bytes.
|
||||
pub fn bytes_mut(&mut self) -> &mut [u8] {
|
||||
self.buf_mut().deref_mut()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,64 @@
|
||||
//! Extension traits for converting `Ref<B, T>` into references backed by
|
||||
//! standard slices.
|
||||
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
/// A trait for converting a `Ref<B, T>` into a `Ref<&[u8], T>`.
|
||||
///
|
||||
/// This can be useful when you need a reference that is tied to a slice rather
|
||||
/// than the original buffer type `B`.
|
||||
///
|
||||
/// Note: This trait is implemented to [`Ref`] of byte slices (`&[u8]`).
|
||||
pub trait ZerocopyEmancipateExt<B, T> {
|
||||
/// Converts this reference into a reference backed by a byte slice.
|
||||
/// Converts this reference into a reference backed by a plain byte slice.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use std::ops::Deref;
|
||||
/// # use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopyEmancipateExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data(u32);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 4]);
|
||||
/// let bytes = AlignedBuf([0xAA, 0xBB, 0xCC, 0xDD]);
|
||||
/// let r = Ref::<&[u8], Data>::new(&bytes.0).unwrap();
|
||||
/// let emancipated: Ref<&[u8], Data> = r.emancipate(); // same data, but guaranteed &[u8] backing
|
||||
/// assert_eq!(emancipated.0, 0xDDCCBBAA);
|
||||
/// ```
|
||||
fn emancipate(&self) -> Ref<&[u8], T>;
|
||||
}
|
||||
|
||||
/// A trait for converting a `Ref<B, T>` into a mutable `Ref<&mut [u8], T>`.
|
||||
///
|
||||
/// Similar to [`ZerocopyEmancipateExt`], but for mutable references.
|
||||
///
|
||||
/// Note: this trait is implemented to [`Ref`] of mutable byte
|
||||
/// slices (`&mut [u8]`).
|
||||
pub trait ZerocopyEmancipateMutExt<B, T> {
|
||||
/// Converts this reference into a mutable reference backed by a byte slice.
|
||||
/// Converts this reference into a mutable reference backed by a plain
|
||||
/// mutable byte slice.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
|
||||
/// # use rosenpass_util::zerocopy::{ZerocopyEmancipateMutExt};
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data(u32);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 4]);
|
||||
/// let mut bytes = AlignedBuf([0xAA, 0xBB, 0xCC, 0xDD]);
|
||||
/// let mut r = Ref::<&mut [u8], Data>::new(&mut bytes.0).unwrap();
|
||||
/// let mut emancipated: Ref<&mut [u8], Data> = r.emancipate_mut(); // same data, but guaranteed &[u8] backing
|
||||
/// assert_eq!(emancipated.0, 0xDDCCBBAA);
|
||||
/// emancipated.0 = 0x33221100;
|
||||
/// drop(emancipated);
|
||||
/// assert_eq!(bytes.0, [0x00, 0x11, 0x22, 0x33]);
|
||||
/// ```
|
||||
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,113 @@
|
||||
//! Extension traits for parsing slices into [`zerocopy::Ref`] values using the
|
||||
//! [`RefMaker`] abstraction.
|
||||
|
||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::RefMaker;
|
||||
|
||||
/// Extension trait for zero-copy slice operations.
|
||||
/// Extension trait for performing zero-copy parsing operations on byte slices.
|
||||
///
|
||||
/// This trait adds methods for creating [`Ref`] references from
|
||||
/// slices by using the [`RefMaker`] type internally.
|
||||
pub trait ZerocopySliceExt: Sized + ByteSlice {
|
||||
/// Creates a new `RefMaker` for the given slice.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
|
||||
///
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data(u32);
|
||||
///
|
||||
/// let rm: RefMaker<&[u8], Data> = [3,0,0,0].zk_ref_maker();
|
||||
/// assert_eq!(rm.bytes(), &[3,0,0,0]);
|
||||
/// assert_eq!(rm.parse().unwrap().0, 3);
|
||||
/// ```
|
||||
fn zk_ref_maker<T>(self) -> RefMaker<Self, T> {
|
||||
RefMaker::<Self, T>::new(self)
|
||||
}
|
||||
|
||||
/// Parses the slice into a zero-copy reference.
|
||||
/// Parses the given slice into a zero-copy reference of the given type `T`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopySliceExt;
|
||||
///
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data(u16, u16);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 4]);
|
||||
/// let bytes = AlignedBuf([0x01,0x02,0x03,0x04]);
|
||||
/// let data_ref = bytes.0.zk_parse::<Data>().unwrap();
|
||||
/// assert_eq!(data_ref.0, 0x0201);
|
||||
/// assert_eq!(data_ref.1, 0x0403);
|
||||
/// ```
|
||||
fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().parse()
|
||||
}
|
||||
|
||||
/// Parses a prefix of the slice into a zero-copy reference.
|
||||
///
|
||||
/// Uses only the first [`std::mem::size_of::<T>()`] bytes of `T`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopySliceExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Header(u32);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 8]);
|
||||
/// let bytes = AlignedBuf([0xAA, 0xBB, 0xCC, 0xDD,
|
||||
/// 0x00, 0x10, 0x20, 0x30]);
|
||||
///
|
||||
/// let header_ref = bytes.0.zk_parse_prefix::<Header>().unwrap();
|
||||
/// assert_eq!(header_ref.0, 0xDDCCBBAA);
|
||||
/// ```
|
||||
fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().from_prefix()?.parse()
|
||||
}
|
||||
|
||||
/// Parses a suffix of the slice into a zero-copy reference.
|
||||
///
|
||||
/// Uses only the last [`std::mem::size_of::<T>()`] bytes of `T`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopySliceExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Header(u32);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 8]);
|
||||
/// let bytes = AlignedBuf([0xAA, 0xBB, 0xCC, 0xDD,
|
||||
/// 0x00, 0x10, 0x20, 0x30]);
|
||||
///
|
||||
/// let header_ref = bytes.0.zk_parse_suffix::<Header>().unwrap();
|
||||
/// assert_eq!(header_ref.0, 0x30201000);
|
||||
/// ```
|
||||
fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().from_suffix()?.parse()
|
||||
}
|
||||
@@ -27,19 +115,90 @@ pub trait ZerocopySliceExt: Sized + ByteSlice {
|
||||
|
||||
impl<B: ByteSlice> ZerocopySliceExt for B {}
|
||||
|
||||
/// Extension trait for zero-copy slice operations with mutable slices.
|
||||
/// Extension trait for zero-copy parsing of mutable slices with zeroization
|
||||
/// capabilities.
|
||||
///
|
||||
/// Provides convenience methods to create zero-initialized references.
|
||||
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
|
||||
/// Creates a new zeroed reference from the entire slice.
|
||||
/// Creates a new zeroized reference from the entire slice.
|
||||
///
|
||||
/// This zeroizes the slice first, then provides a `Ref`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data([u8; 4]);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 4]);
|
||||
/// let mut bytes = AlignedBuf([0xFF; 4]);
|
||||
/// let data_ref = bytes.0.zk_zeroized::<Data>().unwrap();
|
||||
/// assert_eq!(data_ref.0, [0,0,0,0]);
|
||||
/// assert_eq!(bytes.0, [0, 0, 0, 0]);
|
||||
/// ```
|
||||
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().make_zeroized()
|
||||
}
|
||||
|
||||
/// Creates a new zeroed reference from a prefix of the slice.
|
||||
/// Creates a new zeroized reference from the prefix of the slice.
|
||||
///
|
||||
/// Zeroizes the first `target_size()` bytes of the slice, then returns a
|
||||
/// `Ref`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data([u8; 4]);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 6]);
|
||||
/// let mut bytes = AlignedBuf([0xFF; 6]);
|
||||
/// let data_ref = bytes.0.zk_zeroized_from_prefix::<Data>().unwrap();
|
||||
/// assert_eq!(data_ref.0, [0,0,0,0]);
|
||||
/// assert_eq!(bytes.0, [0, 0, 0, 0, 0xFF, 0xFF]);
|
||||
/// ```
|
||||
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().from_prefix()?.make_zeroized()
|
||||
}
|
||||
|
||||
/// Creates a new zeroed reference from a suffix of the slice.
|
||||
/// Creates a new zeroized reference from the suffix of the slice.
|
||||
///
|
||||
/// Zeroizes the last `target_size()` bytes of the slice, then returns a
|
||||
/// `Ref`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the slice is too small.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
/// # use rosenpass_util::zerocopy::ZerocopyMutSliceExt;
|
||||
/// #[derive(FromBytes, FromZeroes, AsBytes)]
|
||||
/// #[repr(C)]
|
||||
/// struct Data([u8; 4]);
|
||||
/// #[repr(align(4))]
|
||||
/// struct AlignedBuf([u8; 6]);
|
||||
/// let mut bytes = AlignedBuf([0xFF; 6]);
|
||||
/// let data_ref = bytes.0.zk_zeroized_from_suffix::<Data>().unwrap();
|
||||
/// assert_eq!(data_ref.0, [0,0,0,0]);
|
||||
/// assert_eq!(bytes.0, [0xFF, 0xFF, 0, 0, 0, 0]);
|
||||
/// ```
|
||||
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||
self.zk_ref_maker().from_suffix()?.make_zeroized()
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -28,7 +28,7 @@ derive_builder = { workspace = true }
|
||||
postcard = { workspace = true }
|
||||
# Problem in CI, unknown reasons: dependency (libc) specified without providing a local path, Git repository, version, or workspace dependency to use
|
||||
# Maybe something about the combination of features and optional crates?
|
||||
rustix = { version = "0.38.41", optional = true }
|
||||
rustix = { version = "0.38.42", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
|
||||
# Mio broker client
|
||||
|
||||
Reference in New Issue
Block a user