mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
94 Commits
dev/karo/a
...
dev/blipp/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf67344e86 | ||
|
|
d2539e445f | ||
|
|
6dc58cc6c1 | ||
|
|
e3d16966c9 | ||
|
|
a5e6af4b49 | ||
|
|
24a71977f0 | ||
|
|
5f0ac579d7 | ||
|
|
4df994b5f0 | ||
|
|
e4e0a9e661 | ||
|
|
742e037936 | ||
|
|
b5848af799 | ||
|
|
4982e40084 | ||
|
|
c1ae3268c6 | ||
|
|
524ec68f3f | ||
|
|
184603aa2c | ||
|
|
ec6706ffeb | ||
|
|
7571670e71 | ||
|
|
0d7dd99d96 | ||
|
|
c78a9cb777 | ||
|
|
dd0db53e8b | ||
|
|
422acf9891 | ||
|
|
877c15a018 | ||
|
|
55d7f8b1c1 | ||
|
|
199ff63a06 | ||
|
|
47b556e317 | ||
|
|
f87e2cb31b | ||
|
|
58e1c8fbff | ||
|
|
c89c7d7acf | ||
|
|
a5b876f119 | ||
|
|
c2f50f47b3 | ||
|
|
53168dc62d | ||
|
|
2cfe703118 | ||
|
|
a2d7c3aaa6 | ||
|
|
1aa111570e | ||
|
|
a91d61f9f0 | ||
|
|
ff7827c24e | ||
|
|
255e377d29 | ||
|
|
50505d81cc | ||
|
|
10484cc6d4 | ||
|
|
d27e602f43 | ||
|
|
73f6b33dbb | ||
|
|
a279dfc0b1 | ||
|
|
caf2f6bfec | ||
|
|
d398ad369e | ||
|
|
00696321ff | ||
|
|
d807a1bca7 | ||
|
|
4daf97b2ee | ||
|
|
b394e302ab | ||
|
|
198bc2d5f2 | ||
|
|
fc2f535eae | ||
|
|
302e249f08 | ||
|
|
d8fe3eba5f | ||
|
|
35519e7baa | ||
|
|
78af5d1dc4 | ||
|
|
61b8b28e86 | ||
|
|
26f77924f8 | ||
|
|
2e0e2cfa0c | ||
|
|
9cc860fdeb | ||
|
|
a537eb3e1b | ||
|
|
ea233bf137 | ||
|
|
db8796ab40 | ||
|
|
0353c82729 | ||
|
|
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 |
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -109,9 +109,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.94"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@@ -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.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.215"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -49,10 +49,10 @@ 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.217", features = ["derive"] }
|
||||
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
|
||||
anyhow = { version = "1.0.95", features = ["backtrace", "std"] }
|
||||
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||
'classic_mceliece',
|
||||
@@ -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,10 @@ main() {
|
||||
open="1"
|
||||
fi
|
||||
|
||||
exc cargo llvm-cov --all-features --workspace --doctests
|
||||
exc cargo llvm-cov --all-features --workspace --doctests --branch
|
||||
|
||||
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins
|
||||
exc mv -v target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/
|
||||
exc rm -rf "${OUTPUT_DIR}"
|
||||
exc mkdir -p "${OUTPUT_DIR}"
|
||||
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
|
||||
|
||||
@@ -50,6 +50,8 @@ rustPlatform.buildRustPackage {
|
||||
cargoBuildOptions = [ "--package" package ];
|
||||
cargoTestOptions = [ "--package" package ];
|
||||
|
||||
buildFeatures = [ "experiment_api" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
cargoLock = {
|
||||
|
||||
@@ -26,6 +26,10 @@ required-features = ["experiment_api", "internal_testing"]
|
||||
name = "api-integration-tests-api-setup"
|
||||
required-features = ["experiment_api", "internal_testing"]
|
||||
|
||||
[[test]]
|
||||
name = "gen-ipc-msg-types"
|
||||
required-features = ["experiment_api", "internal_testing", "internal_bin_gen_ipc_msg_types"]
|
||||
|
||||
[[bench]]
|
||||
name = "handshake"
|
||||
harness = false
|
||||
@@ -91,3 +95,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)'] }
|
||||
|
||||
@@ -8,210 +8,250 @@ use super::{
|
||||
};
|
||||
|
||||
pub trait ByteSliceRefExt: ByteSlice {
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
|
||||
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker().parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_prefix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_suffix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
|
||||
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_request_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker().parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_prefix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_prefix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
|
||||
/// [RefMaker::from_suffix], and
|
||||
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
|
||||
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
|
||||
self.msg_type_maker()
|
||||
.from_suffix()?
|
||||
.parse_response_msg_type()
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse] in chaining.
|
||||
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse_from_prefix] in chaining.
|
||||
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [RequestRef::parse_from_suffix] in chaining.
|
||||
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
|
||||
RequestRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse] in chaining.
|
||||
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse_from_prefix] in chaining.
|
||||
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_prefix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the use of [ResponseRef::parse_from_suffix] in chaining.
|
||||
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
|
||||
ResponseRef::parse_from_suffix(self)
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn supply_keypair_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn supply_keypair_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_listen_socket_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_listen_socket_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_listen_socket_response(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_listen_socket_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_listen_socket_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_psk_broker_request_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_psk_broker_request_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
|
||||
self.zk_parse_suffix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
|
||||
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
|
||||
self.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
|
||||
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
|
||||
fn add_psk_broker_response_from_prefix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
self.zk_parse_prefix()
|
||||
}
|
||||
|
||||
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
|
||||
fn add_psk_broker_response_from_suffix(
|
||||
self,
|
||||
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
|
||||
|
||||
@@ -4,16 +4,41 @@ use rosenpass_util::zerocopy::RefMaker;
|
||||
|
||||
use super::RawMsgType;
|
||||
|
||||
/// Trait implemented by all the Rosenpass API message types.
|
||||
///
|
||||
/// Implemented by the message as including the message envelope; e.g.
|
||||
/// [crate::api::PingRequest] but not by [crate::api::PingRequestPayload].
|
||||
pub trait Message {
|
||||
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
|
||||
type Payload;
|
||||
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
|
||||
type MessageClass: Into<RawMsgType>;
|
||||
/// The specific message type in the [Self::MessageClass].
|
||||
/// E.g. this is [crate::api::RequestMsgType::Ping] for [crate::api::PingRequest]
|
||||
const MESSAGE_TYPE: Self::MessageClass;
|
||||
|
||||
/// Wraps the payload into the envelope
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::from_payload]
|
||||
fn from_payload(payload: Self::Payload) -> Self;
|
||||
/// Initialize the message;
|
||||
/// just sets the message type [crate::api::Envelope::msg_type].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::init]
|
||||
fn init(&mut self);
|
||||
/// Initialize the message from a raw buffer: Zeroize the buffer and then call [Self::init].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [crate::api::PingRequest::setup]
|
||||
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
|
||||
}
|
||||
|
||||
/// Additional convenience functions for working with [rosenpass_util::zerocopy::RefMaker]
|
||||
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
|
||||
}
|
||||
@@ -23,6 +48,27 @@ where
|
||||
B: ByteSliceMut,
|
||||
T: Message,
|
||||
{
|
||||
/// Initialize the message using [Message::setup].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::api::{
|
||||
/// PingRequest, ZerocopyResponseMakerSetupMessageExt, PING_REQUEST,
|
||||
/// };
|
||||
/// use rosenpass_util::zerocopy::RefMaker;
|
||||
/// use std::mem::size_of;
|
||||
///
|
||||
/// let mut buf = [0u8; { size_of::<PingRequest>() }];
|
||||
///
|
||||
/// let rm = RefMaker::<&mut [u8], PingRequest>::new(&mut buf);
|
||||
/// let msg: zerocopy::Ref<_, PingRequest> = rm.setup_msg()?;
|
||||
///
|
||||
/// let t = msg.msg_type; // Deal with unaligned read
|
||||
/// assert_eq!(t, PING_REQUEST);
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
|
||||
T::setup(self.into_buf())
|
||||
}
|
||||
|
||||
@@ -35,10 +35,15 @@ const ADD_PSK_BROKER_REQUEST: RawMsgType =
|
||||
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
|
||||
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
|
||||
|
||||
/// Message properties global to the message type
|
||||
pub trait MessageAttributes {
|
||||
/// Get the size of the message
|
||||
///
|
||||
/// # Exampleds
|
||||
fn message_size(&self) -> usize;
|
||||
}
|
||||
|
||||
/// API request message types as an enum
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum RequestMsgType {
|
||||
Ping,
|
||||
@@ -47,6 +52,7 @@ pub enum RequestMsgType {
|
||||
AddPskBroker,
|
||||
}
|
||||
|
||||
/// API response messages types as an enum
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum ResponseMsgType {
|
||||
Ping,
|
||||
@@ -131,8 +137,17 @@ impl From<ResponseMsgType> for RawMsgType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [RawMsgType].
|
||||
///
|
||||
/// We are using an extension trait rather than just using methods
|
||||
/// because [RawMsgType] is a type alias, so we can not define methods
|
||||
/// on it.
|
||||
pub trait RawMsgTypeExt {
|
||||
/// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
|
||||
/// implementation
|
||||
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
|
||||
/// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
|
||||
/// implementation
|
||||
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
|
||||
}
|
||||
|
||||
@@ -146,8 +161,11 @@ impl RawMsgTypeExt for RawMsgType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
|
||||
pub trait RefMakerRawMsgTypeExt {
|
||||
/// Parse a request message type from bytes
|
||||
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
|
||||
/// Parse a response message type from bytes
|
||||
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Boring, repetitive code related to message parsing for the API.
|
||||
//!
|
||||
//! Most of this should be automatically generated though some derive macro at some point.
|
||||
|
||||
mod byte_slice_ext;
|
||||
mod message_trait;
|
||||
mod message_type;
|
||||
|
||||
@@ -3,11 +3,14 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
|
||||
|
||||
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
|
||||
|
||||
/// Size required to fit any message in binary form
|
||||
/// Size required to fit any request message in binary form
|
||||
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
|
||||
/// Size required to fit any response message in binary form
|
||||
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
|
||||
/// Maximum number of file descriptors that can be sent in a request.
|
||||
pub const MAX_REQUEST_FDS: usize = 2;
|
||||
|
||||
/// Message envelope for API messages
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
@@ -17,9 +20,12 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
pub payload: M,
|
||||
}
|
||||
|
||||
/// Message envelope for API requests
|
||||
pub type RequestEnvelope<M> = Envelope<M>;
|
||||
/// Message envelope for API responses
|
||||
pub type ResponseEnvelope<M> = Envelope<M>;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingRequestPayload {
|
||||
@@ -27,9 +33,11 @@ pub struct PingRequestPayload {
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
|
||||
|
||||
impl PingRequest {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingRequestPayload { echo })
|
||||
}
|
||||
@@ -58,6 +66,7 @@ impl Message for PingRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct PingResponsePayload {
|
||||
@@ -65,9 +74,11 @@ pub struct PingResponsePayload {
|
||||
pub echo: [u8; 256],
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
|
||||
|
||||
impl PingResponse {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(echo: [u8; 256]) -> Self {
|
||||
Self::from_payload(PingResponsePayload { echo })
|
||||
}
|
||||
@@ -96,10 +107,12 @@ impl Message for PingResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
|
||||
|
||||
impl Default for SupplyKeypairRequest {
|
||||
@@ -109,6 +122,7 @@ impl Default for SupplyKeypairRequest {
|
||||
}
|
||||
|
||||
impl SupplyKeypairRequest {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(SupplyKeypairRequestPayload {})
|
||||
}
|
||||
@@ -137,25 +151,35 @@ impl Message for SupplyKeypairRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod supply_keypair_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||
// TODO: This is not actually part of the API. Remove.
|
||||
/// TODO: This is not actually part of the API. Remove.
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 3;
|
||||
/// TODO: Deprectaed, remove
|
||||
#[allow(missing_docs)]
|
||||
pub const IO_ERROR: u128 = 4;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct SupplyKeypairResponsePayload {
|
||||
#[allow(missing_docs)]
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
|
||||
|
||||
impl SupplyKeypairResponse {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(SupplyKeypairResponsePayload { status })
|
||||
}
|
||||
@@ -184,10 +208,12 @@ impl Message for SupplyKeypairResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
|
||||
|
||||
impl Default for AddListenSocketRequest {
|
||||
@@ -197,6 +223,7 @@ impl Default for AddListenSocketRequest {
|
||||
}
|
||||
|
||||
impl AddListenSocketRequest {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(AddListenSocketRequestPayload {})
|
||||
}
|
||||
@@ -225,21 +252,28 @@ impl Message for AddListenSocketRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod add_listen_socket_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddListenSocketResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
|
||||
|
||||
impl AddListenSocketResponse {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(AddListenSocketResponsePayload { status })
|
||||
}
|
||||
@@ -268,19 +302,23 @@ impl Message for AddListenSocketResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerRequestPayload {}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
|
||||
|
||||
impl Default for AddPskBrokerRequest {
|
||||
#[allow(missing_docs)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddPskBrokerRequest {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new() -> Self {
|
||||
Self::from_payload(AddPskBrokerRequestPayload {})
|
||||
}
|
||||
@@ -309,21 +347,28 @@ impl Message for AddPskBrokerRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod add_psk_broker_response_status {
|
||||
#[allow(missing_docs)]
|
||||
pub const OK: u128 = 0;
|
||||
#[allow(missing_docs)]
|
||||
pub const INVALID_REQUEST: u128 = 1;
|
||||
#[allow(missing_docs)]
|
||||
pub const INTERNAL_ERROR: u128 = 2;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
pub struct AddPskBrokerResponsePayload {
|
||||
pub status: u128,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
|
||||
|
||||
impl AddPskBrokerResponse {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(status: u128) -> Self {
|
||||
Self::from_payload(AddPskBrokerResponsePayload { status })
|
||||
}
|
||||
|
||||
@@ -4,24 +4,63 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
|
||||
|
||||
/// Helper for producing API message request references, [RequestRef].
|
||||
///
|
||||
/// This is to [RequestRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||
/// [zerocopy::Ref].
|
||||
struct RequestRefMaker<B> {
|
||||
buf: B,
|
||||
msg_type: RequestMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> RequestRef<B> {
|
||||
/// Produce a [RequestRef] from a raw message buffer,
|
||||
/// reading the type from the buffer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zerocopy::AsBytes;
|
||||
///
|
||||
/// use rosenpass::api::{PingRequest, RequestRef, RequestMsgType};
|
||||
///
|
||||
/// let msg = PingRequest::new([0u8; 256]);
|
||||
///
|
||||
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||
/// let typ = msg.msg_type;
|
||||
/// assert_eq!(typ, rosenpass::api::PING_REQUEST);
|
||||
///
|
||||
/// let buf = msg.as_bytes();
|
||||
/// let msg_ref = RequestRef::parse(buf)?;
|
||||
/// assert!(matches!(msg_ref, RequestRef::Ping(_)));
|
||||
///
|
||||
/// assert_eq!(msg_ref.message_type(), RequestMsgType::Ping);
|
||||
///
|
||||
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
RequestRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
/// Get the message type [Self] contains
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse]
|
||||
pub fn message_type(&self) -> RequestMsgType {
|
||||
match self {
|
||||
Self::Ping(_) => RequestMsgType::Ping,
|
||||
@@ -110,6 +149,7 @@ impl<B: ByteSlice> RequestRefMaker<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a API message response, typed as an enum.
|
||||
pub enum RequestRef<B> {
|
||||
Ping(Ref<B, PingRequest>),
|
||||
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
|
||||
@@ -121,6 +161,11 @@ impl<B> RequestRef<B>
|
||||
where
|
||||
B: ByteSlice,
|
||||
{
|
||||
/// Access the byte data of this reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse].
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes(),
|
||||
@@ -135,6 +180,7 @@ impl<B> RequestRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
/// Access the byte data of this reference; mutably
|
||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes_mut(),
|
||||
|
||||
@@ -6,23 +6,29 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
use super::{Message, PingRequest, PingResponse};
|
||||
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
|
||||
|
||||
/// Extension trait for [Message]s that are requests messages
|
||||
pub trait RequestMsg: Sized + Message {
|
||||
/// The response message belonging to this request message
|
||||
type ResponseMsg: ResponseMsg;
|
||||
|
||||
/// Construct a response make for this particular message
|
||||
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
|
||||
buf.zk_ref_maker()
|
||||
}
|
||||
|
||||
/// Setup a response maker (through [Message::setup]) for this request message type
|
||||
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).setup_msg()
|
||||
}
|
||||
|
||||
/// Setup a response maker from a buffer prefix (through [Message::setup]) for this request message type
|
||||
fn setup_response_from_prefix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
|
||||
}
|
||||
|
||||
/// Setup a response maker from a buffer suffix (through [Message::setup]) for this request message type
|
||||
fn setup_response_from_suffix<B: ByteSliceMut>(
|
||||
buf: B,
|
||||
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
|
||||
@@ -30,6 +36,7 @@ pub trait RequestMsg: Sized + Message {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for [Message]s that are response messages
|
||||
pub trait ResponseMsg: Message {
|
||||
type RequestMsg: RequestMsg;
|
||||
}
|
||||
@@ -66,20 +73,25 @@ impl ResponseMsg for super::AddPskBrokerResponse {
|
||||
type RequestMsg = super::AddPskBrokerRequest;
|
||||
}
|
||||
|
||||
/// Request and response for the [crate::api::RequestMsgType::Ping] message type
|
||||
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
|
||||
/// Request and response for the [crate::api::RequestMsgType::SupplyKeypair] message type
|
||||
pub type SupplyKeypairPair<B1, B2> = (
|
||||
Ref<B1, super::SupplyKeypairRequest>,
|
||||
Ref<B2, super::SupplyKeypairResponse>,
|
||||
);
|
||||
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
|
||||
pub type AddListenSocketPair<B1, B2> = (
|
||||
Ref<B1, super::AddListenSocketRequest>,
|
||||
Ref<B2, super::AddListenSocketResponse>,
|
||||
);
|
||||
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
|
||||
pub type AddPskBrokerPair<B1, B2> = (
|
||||
Ref<B1, super::AddPskBrokerRequest>,
|
||||
Ref<B2, super::AddPskBrokerResponse>,
|
||||
);
|
||||
|
||||
/// A pair of references to messages; request and response each.
|
||||
pub enum RequestResponsePair<B1, B2> {
|
||||
Ping(PingPair<B1, B2>),
|
||||
SupplyKeypair(SupplyKeypairPair<B1, B2>),
|
||||
@@ -116,6 +128,7 @@ where
|
||||
B1: ByteSlice,
|
||||
B2: ByteSlice,
|
||||
{
|
||||
/// Returns a tuple to both the request and the response message
|
||||
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
@@ -141,10 +154,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the request message
|
||||
pub fn request(&self) -> RequestRef<&[u8]> {
|
||||
self.both().0
|
||||
}
|
||||
|
||||
/// Returns the response message
|
||||
pub fn response(&self) -> ResponseRef<&[u8]> {
|
||||
self.both().1
|
||||
}
|
||||
@@ -155,6 +170,7 @@ where
|
||||
B1: ByteSliceMut,
|
||||
B2: ByteSliceMut,
|
||||
{
|
||||
/// Returns a mutable tuple to both the request and the response message
|
||||
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
|
||||
match self {
|
||||
Self::Ping((req, res)) => {
|
||||
@@ -180,10 +196,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the request message, mutably
|
||||
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
|
||||
self.both_mut().0
|
||||
}
|
||||
|
||||
/// Returns the response message, mutably
|
||||
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
|
||||
self.both_mut().1
|
||||
}
|
||||
|
||||
@@ -5,24 +5,66 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
|
||||
|
||||
/// Helper for producing API message response references, [ResponseRef].
|
||||
///
|
||||
/// This is to [ResponseRef] as [rosenpass_util::zerocopy::RefMaker] is to
|
||||
/// [zerocopy::Ref].
|
||||
struct ResponseRefMaker<B> {
|
||||
/// Buffer we are referencing
|
||||
buf: B,
|
||||
/// Message type we are producing
|
||||
msg_type: ResponseMsgType,
|
||||
}
|
||||
|
||||
impl<B: ByteSlice> ResponseRef<B> {
|
||||
/// Produce a [ResponseRef] from a raw message buffer,
|
||||
/// reading the type from the buffer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use zerocopy::AsBytes;
|
||||
///
|
||||
/// use rosenpass::api::{PingResponse, ResponseRef, ResponseMsgType};
|
||||
/// // Produce the original PingResponse
|
||||
/// let msg = PingResponse::new([0u8; 256]);
|
||||
///
|
||||
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
|
||||
/// let typ = msg.msg_type;
|
||||
/// assert_eq!(typ, rosenpass::api::PING_RESPONSE);
|
||||
///
|
||||
/// // Parse as a message type
|
||||
/// let buf = msg.as_bytes();
|
||||
/// let msg_ref = ResponseRef::parse(buf)?;
|
||||
/// assert!(matches!(msg_ref, ResponseRef::Ping(_)));
|
||||
///
|
||||
/// // Buffers and message types of course match what we expect
|
||||
/// assert_eq!(msg_ref.message_type(), ResponseMsgType::Ping);
|
||||
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn parse(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
|
||||
}
|
||||
|
||||
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
|
||||
/// reading the type from the buffer.
|
||||
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
|
||||
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
|
||||
}
|
||||
|
||||
/// Get the message type [Self] contains
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse]
|
||||
pub fn message_type(&self) -> ResponseMsgType {
|
||||
match self {
|
||||
Self::Ping(_) => ResponseMsgType::Ping,
|
||||
@@ -111,6 +153,7 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a API message response, typed.
|
||||
pub enum ResponseRef<B> {
|
||||
Ping(Ref<B, PingResponse>),
|
||||
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
|
||||
@@ -122,6 +165,11 @@ impl<B> ResponseRef<B>
|
||||
where
|
||||
B: ByteSlice,
|
||||
{
|
||||
/// Access the byte data of this reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::parse].
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes(),
|
||||
@@ -136,6 +184,7 @@ impl<B> ResponseRef<B>
|
||||
where
|
||||
B: ByteSliceMut,
|
||||
{
|
||||
/// Access the byte data of this reference; mutably
|
||||
pub fn bytes_mut(&mut self) -> &[u8] {
|
||||
match self {
|
||||
Self::Ping(r) => r.bytes_mut(),
|
||||
|
||||
@@ -2,10 +2,21 @@ use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, Req
|
||||
use std::{collections::VecDeque, os::fd::OwnedFd};
|
||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||
|
||||
/// The rosenpass API implementation functions.
|
||||
///
|
||||
/// Implemented by [crate::api::ApiHandler].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
pub trait Server {
|
||||
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||
///
|
||||
/// It merely takes a buffer and returns that same buffer.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn ping(
|
||||
&mut self,
|
||||
req: &PingRequest,
|
||||
@@ -54,6 +65,10 @@ pub trait Server {
|
||||
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
|
||||
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
|
||||
/// backed file descriptor if the server keys are not backed by a file system file.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn supply_keypair(
|
||||
&mut self,
|
||||
req: &super::SupplyKeypairRequest,
|
||||
@@ -80,8 +95,13 @@ pub trait Server {
|
||||
///
|
||||
/// # Description
|
||||
///
|
||||
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
|
||||
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform key
|
||||
/// key exchanges using the Rosenpass protocol.
|
||||
/// cryptographic key exchanges via the Rosenpass protocol.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn add_listen_socket(
|
||||
&mut self,
|
||||
req: &super::AddListenSocketRequest,
|
||||
@@ -89,6 +109,31 @@ pub trait Server {
|
||||
res: &mut super::AddListenSocketResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Supply a new PSK broker listen socket through file descriptor passing via the API
|
||||
///
|
||||
/// This implements the handler for the [crate::api::RequestMsgType::AddPskBroker] API message.
|
||||
///
|
||||
/// # File descriptors
|
||||
///
|
||||
/// 1. The listen socket; must be backed by a unix domain stream socket
|
||||
///
|
||||
/// # API Return Status
|
||||
///
|
||||
/// 1. [crate::api::add_psk_broker_response_status::OK] - Indicates success
|
||||
/// 2. [crate::api::add_psk_broker_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||
/// - Missing file descriptors for public key
|
||||
/// - Invalid file descriptor type
|
||||
/// 3. [crate::api::add_psk_broker_response_status::INTERNAL_ERROR] – Some other, non-fatal error
|
||||
/// occured. Check the logs on log
|
||||
///
|
||||
/// # Description
|
||||
///
|
||||
/// This endpoint allows you to supply a UDP listen socket; it will be used to transmit
|
||||
/// cryptographic keys exchanged to WireGuard.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See the example of how to use the API in [crate::api].
|
||||
fn add_psk_broker(
|
||||
&mut self,
|
||||
req: &super::AddPskBrokerRequest,
|
||||
@@ -96,6 +141,11 @@ pub trait Server {
|
||||
res: &mut super::AddPskBrokerResponse,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Similar to [Self::handle_message], but takes a [RequestResponsePair]
|
||||
/// instead of taking to separate byte buffers.
|
||||
///
|
||||
/// I.e. this function uses the explicit type tag encoded in [RequestResponsePair]
|
||||
/// rather than reading the type tag from the request buffer.
|
||||
fn dispatch<ReqBuf, ResBuf>(
|
||||
&mut self,
|
||||
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
|
||||
@@ -117,6 +167,14 @@ pub trait Server {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by [crate::api::mio::MioConnection] when a new API request was received.
|
||||
///
|
||||
/// The parameters are:
|
||||
///
|
||||
/// - `req` – A buffer containing the request
|
||||
/// - `res_fds` – A list of file descriptors received during the API call (i.e. this is used
|
||||
/// with unix socket file descriptor passing)
|
||||
/// - `res` – The buffer to store the response in.
|
||||
fn handle_message<ReqBuf, ResBuf>(
|
||||
&mut self,
|
||||
req: ReqBuf,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::config::Rosenpass as RosenpassConfig;
|
||||
|
||||
use super::config::ApiConfig;
|
||||
|
||||
/// Additional command line arguments for the API
|
||||
#[cfg(feature = "experiment_api")]
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ApiCli {
|
||||
@@ -27,10 +28,14 @@ pub struct ApiCli {
|
||||
}
|
||||
|
||||
impl ApiCli {
|
||||
/// Copy the parameters set here into the [RosenpassConfig].
|
||||
/// Forwards to [Self::apply_to_api_config]:
|
||||
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
|
||||
self.apply_to_api_config(&mut cfg.api)
|
||||
}
|
||||
|
||||
/// Fills the values from [ApiConfig::listen_path], [ApiConfig::listen_fd], and
|
||||
/// [ApiConfig::stream_fd] with the values from [Self]
|
||||
pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> {
|
||||
cfg.listen_path.extend_from_slice(&self.api_listen_path);
|
||||
cfg.listen_fd.extend_from_slice(&self.api_listen_fd);
|
||||
|
||||
@@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::app_server::AppServer;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
/// Configuration options for the Rosenpass API
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
|
||||
pub struct ApiConfig {
|
||||
/// Where in the file-system to create the unix socket the rosenpass API will be listening for
|
||||
/// connections on
|
||||
@@ -23,6 +24,10 @@ pub struct ApiConfig {
|
||||
}
|
||||
|
||||
impl ApiConfig {
|
||||
/// Construct appropriate [UnixListener]s for each of the API
|
||||
/// listeners and connections configured in [Self] and invoke
|
||||
/// [AppServer::add_api_listener] for each to add them to the
|
||||
/// [AppServer].
|
||||
pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
for path in self.listen_path.iter() {
|
||||
srv.add_api_listener(UnixListener::bind(path)?)?;
|
||||
@@ -39,10 +44,12 @@ impl ApiConfig {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sum of all the API sources configured in here
|
||||
pub fn count_api_sources(&self) -> usize {
|
||||
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
|
||||
}
|
||||
|
||||
/// Checks if [Self::count_api_sources] is greater than zero
|
||||
pub fn has_api_sources(&self) -> bool {
|
||||
self.count_api_sources() > 0
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ struct MioConnectionBuffers {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Represents a single connection with an API client.
|
||||
/// Includes the necessary buffers, the [ApiHandler],
|
||||
/// and the [UnixStream] that is used for communication.
|
||||
pub struct MioConnection {
|
||||
io: UnixStream,
|
||||
mio_token: mio::Token,
|
||||
@@ -62,6 +65,8 @@ pub struct MioConnection {
|
||||
}
|
||||
|
||||
impl MioConnection {
|
||||
/// Construct a new [Self] for the given app server from the unix socket stream
|
||||
/// to communicate on.
|
||||
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
|
||||
let mio_token = app_server.mio_token_dispenser.dispense();
|
||||
app_server
|
||||
@@ -88,6 +93,8 @@ impl MioConnection {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if this unix stream should be closed by the enclosing
|
||||
/// structure
|
||||
pub fn should_close(&self) -> bool {
|
||||
let exhausted = self
|
||||
.buffers
|
||||
@@ -97,22 +104,30 @@ impl MioConnection {
|
||||
self.invalid_read && exhausted
|
||||
}
|
||||
|
||||
/// Close and deregister this particular API connection
|
||||
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
|
||||
app_server.mio_poll.registry().deregister(&mut self.io)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the mio token
|
||||
pub fn mio_token(&self) -> mio::Token {
|
||||
self.mio_token
|
||||
}
|
||||
}
|
||||
|
||||
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
|
||||
pub trait MioConnectionContext {
|
||||
/// Reference to the [MioConnection] we are focusing on
|
||||
fn mio_connection(&self) -> &MioConnection;
|
||||
/// Reference to the [AppServer] that contains the [Self::mio_connection]
|
||||
fn app_server(&self) -> &AppServer;
|
||||
/// Mutable reference to the [MioConnection] we are focusing on
|
||||
fn mio_connection_mut(&mut self) -> &mut MioConnection;
|
||||
/// Mutable reference to the [AppServer] that contains the [Self::mio_connection]
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
/// Called by [AppServer::poll] regularly to process any incoming (and outgoing) API messages
|
||||
fn poll(&mut self) -> anyhow::Result<()> {
|
||||
macro_rules! short {
|
||||
($e:expr) => {
|
||||
@@ -133,6 +148,7 @@ pub trait MioConnectionContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called by [Self::poll] to process incoming messages
|
||||
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
|
||||
self.with_buffers_stolen(|this, bufs| {
|
||||
// Acquire request & response. Caller is responsible to make sure
|
||||
@@ -156,6 +172,7 @@ pub trait MioConnectionContext {
|
||||
})
|
||||
}
|
||||
|
||||
/// Called by [Self::poll] to write data in the send buffer to the unix stream
|
||||
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
|
||||
if self.write_buf_mut().exhausted() {
|
||||
return Ok(Some(()));
|
||||
@@ -194,6 +211,7 @@ pub trait MioConnectionContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by [Self::poll] to check for messages to receive
|
||||
fn recv(&mut self) -> anyhow::Result<Option<()>> {
|
||||
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
|
||||
return Ok(None);
|
||||
@@ -257,10 +275,12 @@ pub trait MioConnectionContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards to [MioConnection::mio_token]
|
||||
fn mio_token(&self) -> mio::Token {
|
||||
self.mio_connection().mio_token()
|
||||
}
|
||||
|
||||
/// Forwards to [MioConnection::should_close]
|
||||
fn should_close(&self) -> bool {
|
||||
self.mio_connection().should_close()
|
||||
}
|
||||
@@ -299,6 +319,7 @@ trait MioConnectionContextPrivate: MioConnectionContext {
|
||||
|
||||
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
|
||||
|
||||
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
|
||||
impl<T> ApiHandlerContext for T
|
||||
where
|
||||
T: ?Sized + MioConnectionContext,
|
||||
|
||||
@@ -10,41 +10,59 @@ use crate::app_server::{AppServer, AppServerIoSource};
|
||||
|
||||
use super::{MioConnection, MioConnectionContext};
|
||||
|
||||
/// This is in essence a unix listener for API connections.
|
||||
///
|
||||
/// It contains a number of [UnixListener]s and the associated [MioConnection]s encapsulating [mio::net::UnixListener]s.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MioManager {
|
||||
listeners: Vec<UnixListener>,
|
||||
connections: Vec<Option<MioConnection>>,
|
||||
}
|
||||
|
||||
/// Points at a particular source of IO events inside [MioManager]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MioManagerIoSource {
|
||||
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
|
||||
Listener(usize),
|
||||
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
|
||||
Connection(usize),
|
||||
}
|
||||
|
||||
impl MioManager {
|
||||
/// Construct an empty [Self]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Focus in on a particular [MioConnection] inside a [MioManager]
|
||||
///
|
||||
/// This is mainly used to implement [MioConnectionContext].
|
||||
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
|
||||
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
|
||||
ctx: &'a mut T,
|
||||
/// Index of the connection referenced to by [Self]
|
||||
conn_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
|
||||
/// Produce a MioConnectionContext from the [MioConnectionContext] and the connection index
|
||||
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
|
||||
Self { ctx, conn_idx }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MioManagerContext {
|
||||
/// Reference to the [MioManager]
|
||||
fn mio_manager(&self) -> &MioManager;
|
||||
/// Reference to the [MioManager], mutably
|
||||
fn mio_manager_mut(&mut self) -> &mut MioManager;
|
||||
/// Reference to the [AppServer] this [MioManager] is associated with
|
||||
fn app_server(&self) -> &AppServer;
|
||||
/// Mutable reference to the [AppServer] this [MioManager] is associated with
|
||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||
|
||||
/// Add a new [UnixListener] to listen for API connections on
|
||||
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
|
||||
let srv = self.app_server_mut();
|
||||
let mio_token = srv.mio_token_dispenser.dispense();
|
||||
@@ -64,6 +82,7 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new connection to an API client
|
||||
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
|
||||
let connection = MioConnection::new(self.app_server_mut(), connection)?;
|
||||
let mio_token = connection.mio_token();
|
||||
@@ -84,6 +103,7 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Poll a particular [MioManagerIoSource] in this [MioManager]
|
||||
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
|
||||
use MioManagerIoSource as S;
|
||||
match io_source {
|
||||
@@ -93,12 +113,14 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
|
||||
fn poll(&mut self) -> anyhow::Result<()> {
|
||||
self.accept_connections()?;
|
||||
self.poll_connections()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
|
||||
fn accept_connections(&mut self) -> io::Result<()> {
|
||||
for idx in 0..self.mio_manager_mut().listeners.len() {
|
||||
self.accept_from(idx)?;
|
||||
@@ -106,6 +128,7 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check a particular [UnixListener] managed by this for new connections.
|
||||
fn accept_from(&mut self, idx: usize) -> io::Result<()> {
|
||||
// Accept connection until the socket would block or returns another error
|
||||
// TODO: This currently only adds connections--we eventually need the ability to remove
|
||||
@@ -122,6 +145,7 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
|
||||
fn poll_connections(&mut self) -> anyhow::Result<()> {
|
||||
for idx in 0..self.mio_manager().connections.len() {
|
||||
self.poll_particular_connection(idx)?;
|
||||
@@ -129,6 +153,7 @@ pub trait MioManagerContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call [MioConnectionContext::poll] on a particular connection
|
||||
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
|
||||
if self.mio_manager().connections[idx].is_none() {
|
||||
return Ok(());
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
//! The bulk code relating to the Rosenpass unix socket API
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
#![doc = "```ignore"]
|
||||
#![doc = include_str!("../../tests/api-integration-tests-api-setup.rs")]
|
||||
#![doc = "```"]
|
||||
|
||||
mod api_handler;
|
||||
mod boilerplate;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/// This contains the bulk of the rosenpass server IO handling code whereas
|
||||
/// the actual cryptographic code lives in the [crate::protocol] module
|
||||
use anyhow::bail;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -49,33 +51,78 @@ use crate::{
|
||||
use rosenpass_util::attempt;
|
||||
use rosenpass_util::b64::B64Display;
|
||||
|
||||
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||
/// The maximum size of a base64 encoded symmetric key (estimate)
|
||||
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
|
||||
/// The maximum size of a base64 peer ID (estimate)
|
||||
pub const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
|
||||
|
||||
/// The zero IPv4 address; this is generally used to tell network servers to choose any interface
|
||||
/// when listening
|
||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
/// The zero IPv6 address; this is generally used to tell network servers to choose any interface
|
||||
/// when listening
|
||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
/// Ratio of blocking epoll(7) polls to non-blocking polls at which the rosenpass server
|
||||
/// assumes it is under load (i.e. a DOS attack may be happening)
|
||||
const UNDER_LOAD_RATIO: f64 = 0.5;
|
||||
/// Period at which the DOS detection code updates whether there is an "under load" status
|
||||
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
|
||||
|
||||
const BROKER_ID_BYTES: usize = 8;
|
||||
pub const BROKER_ID_BYTES: usize = 8;
|
||||
|
||||
fn ipv4_any_binding() -> SocketAddr {
|
||||
/// IPv4 address that tells the network layer to listen on any interface
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [AppServer::new].
|
||||
pub fn ipv4_any_binding() -> SocketAddr {
|
||||
// addr, port
|
||||
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
|
||||
}
|
||||
|
||||
fn ipv6_any_binding() -> SocketAddr {
|
||||
/// IPv6 address that tells the network layer to listen on any interface
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [AppServer::new].
|
||||
pub fn ipv6_any_binding() -> SocketAddr {
|
||||
// addr, port, flowinfo, scope_id
|
||||
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
|
||||
}
|
||||
|
||||
/// This is used to assign indices to MIO (epoll) event sources
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MioTokenDispenser {
|
||||
counter: usize,
|
||||
pub counter: usize,
|
||||
}
|
||||
|
||||
impl MioTokenDispenser {
|
||||
/// Produces a single IO event source ID
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Use is quite straightforward:
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::app_server::MioTokenDispenser;
|
||||
/// use mio::Token;
|
||||
///
|
||||
/// let mut dispenser = MioTokenDispenser {
|
||||
/// counter: 0
|
||||
/// };
|
||||
///
|
||||
/// let t1 = dispenser.dispense();
|
||||
/// let t2 = dispenser.dispense();
|
||||
///
|
||||
/// assert_ne!(t1, t2);
|
||||
///
|
||||
/// // If you inspected the output, you would find that the dispenser is really just a counter.
|
||||
/// // Though this is an implementation detail
|
||||
/// assert_eq!(t1, Token(0));
|
||||
/// assert_eq!(t2, Token(1));
|
||||
/// ```
|
||||
///
|
||||
pub fn dispense(&mut self) -> Token {
|
||||
let r = self.counter;
|
||||
self.counter += 1;
|
||||
@@ -83,42 +130,132 @@ impl MioTokenDispenser {
|
||||
}
|
||||
}
|
||||
|
||||
/// List of WireGuard brokers
|
||||
///
|
||||
/// Each WireGuard peer ([AppPeer]) is assigned a broker ([AppPeer::broker_peer]).
|
||||
///
|
||||
/// When a new has been exchanged, then its associated broker is called to transmit the key
|
||||
/// to WireGuard (or to do something else).
|
||||
///
|
||||
/// Brokers live in [AppServer::brokers]. They are added/removed from the AppServer via
|
||||
/// [AppServer::register_broker] and [AppServer::unregister_broker]. PSKs are distributed
|
||||
/// to their respective brokers via [AppPeerPtr::set_psk].
|
||||
///
|
||||
/// Note that the entire broker system is an experimental feature; it is not up to the
|
||||
/// same quality standards as the rest of the code. In particular, the use of [BROKER_ID_BYTES]
|
||||
/// and a hash map is simultaneously overengineered and not really that useful for what it is
|
||||
/// doing.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BrokerStore {
|
||||
/// The collection of WireGuard brokers. See [Self].
|
||||
pub store: HashMap<
|
||||
Public<BROKER_ID_BYTES>,
|
||||
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||
>,
|
||||
}
|
||||
|
||||
/// Reference to a broker imbued with utility methods
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
|
||||
|
||||
/// This is the broker configuration for a particular broker peer
|
||||
#[derive(Debug)]
|
||||
pub struct BrokerPeer {
|
||||
/// Reference to the broker used for this particular peer
|
||||
ptr: BrokerStorePtr,
|
||||
/// Configuration for a WireGuard broker.
|
||||
///
|
||||
/// This is woefully overengineered and there is very little reason why the broker
|
||||
/// configuration should not live in the particular WireGuard broker.
|
||||
peer_cfg: Box<dyn WireguardBrokerCfg>,
|
||||
}
|
||||
|
||||
impl BrokerPeer {
|
||||
/// Create a broker peer
|
||||
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
|
||||
Self { ptr, peer_cfg }
|
||||
}
|
||||
|
||||
/// Retrieve the pointer to WireGuard PSK broker used with this peer
|
||||
pub fn ptr(&self) -> &BrokerStorePtr {
|
||||
&self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// IO/BusinessLogic information for a particular protocol peer.
|
||||
///
|
||||
/// There is a one-to-one correspondence between this struct and [crate::protocol::Peer];
|
||||
/// whereas the struct in the protocol module stores information specific to the cryptographic layer,
|
||||
/// this struct stores information IO information.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AppPeer {
|
||||
/// If set, then [AppServer::output_key] will write generated output keys
|
||||
/// to a file configured here and produce information on standard out to
|
||||
/// notify the calling process that
|
||||
pub outfile: Option<PathBuf>,
|
||||
/// If this option is set, then [AppServer::output_key] will send generated output
|
||||
/// keys to the broker configured here
|
||||
pub broker_peer: Option<BrokerPeer>,
|
||||
/// This is the network address configured for a particular peer at program start.
|
||||
///
|
||||
/// I.e. this is the address the rosenpass program will send [crate::msgs::InitHello]
|
||||
/// packets to, trying to exchange a key.
|
||||
///
|
||||
/// Note that the remote peer may connect with another address. See [Self::current_endpoint].
|
||||
pub initial_endpoint: Option<Endpoint>,
|
||||
/// The network address currently used for a particular peer.
|
||||
///
|
||||
/// This is not necessarily the address that was configured at program start (see [Self::initial_endpoint]),
|
||||
/// because the remote peer can initiate handshakes from an arbitrary network address.
|
||||
///
|
||||
/// If another peer successfully connects to this one from any address, then this field will
|
||||
/// be updated to reflect which address this was.
|
||||
pub current_endpoint: Option<Endpoint>,
|
||||
}
|
||||
|
||||
impl AppPeer {
|
||||
/// Retrieve the [Endpoint] associated with this peer.
|
||||
///
|
||||
/// I.e. the [Self::current_endpoint] if set and [Self::initial_endpoint] otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::app_server::{Endpoint, AppPeer};
|
||||
/// use rosenpass_util::functional::run;
|
||||
///
|
||||
/// let mut peer = AppPeer {
|
||||
/// outfile: None,
|
||||
/// broker_peer: None,
|
||||
/// initial_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:0".to_string())?),
|
||||
/// current_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:1".to_string())?),
|
||||
/// };
|
||||
///
|
||||
/// fn same(a: Option<&Endpoint>, b: Option<&Endpoint>) -> bool {
|
||||
/// if a.is_none() && b.is_none() {
|
||||
/// return true;
|
||||
/// }
|
||||
///
|
||||
/// run(|| Some(std::ptr::eq(a?, b?)) )
|
||||
/// .unwrap_or(a.is_some() == b.is_some())
|
||||
/// }
|
||||
///
|
||||
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||
///
|
||||
/// let mut tmp = None;
|
||||
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
|
||||
///
|
||||
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
|
||||
/// std::mem::swap(&mut tmp, &mut peer.current_endpoint);
|
||||
/// assert!(same(peer.endpoint(), peer.initial_endpoint.as_ref()));
|
||||
///
|
||||
/// peer.initial_endpoint = None;
|
||||
/// peer.current_endpoint = None;
|
||||
/// assert!(peer.endpoint().is_none());
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn endpoint(&self) -> Option<&Endpoint> {
|
||||
self.current_endpoint
|
||||
.as_ref()
|
||||
@@ -126,6 +263,8 @@ impl AppPeer {
|
||||
}
|
||||
}
|
||||
|
||||
/// No longer in use since we have the broker system (see [BrokerPeer])
|
||||
/// TODO: Remove
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WireguardOut {
|
||||
// impl KeyOutput
|
||||
@@ -134,12 +273,20 @@ pub struct WireguardOut {
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
/// Used to indicate whether the rosenpass server is in normal operating
|
||||
/// conditions or under load (i.e. a DOS attack is happening)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum DoSOperation {
|
||||
UnderLoad,
|
||||
Normal,
|
||||
}
|
||||
/// Integration test helpers for AppServer
|
||||
///
|
||||
/// TODO: Remove; this is no way to write integration tests
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [AppServer]
|
||||
#[derive(Debug, Builder)]
|
||||
#[builder(pattern = "owned")]
|
||||
pub struct AppServerTest {
|
||||
@@ -151,41 +298,100 @@ pub struct AppServerTest {
|
||||
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
|
||||
}
|
||||
|
||||
/// This represents a some source of IO operations in the context of the Rosenpass server
|
||||
///
|
||||
/// I.e. this identifies some structure that could be marked as "ready for IO" by [mio]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum AppServerIoSource {
|
||||
/// IO source refers to a socket in [AppServer::sockets]
|
||||
Socket(usize),
|
||||
/// IO source refers to a PSK broker in [AppServer::brokers]
|
||||
PskBroker(Public<BROKER_ID_BYTES>),
|
||||
/// IO source refers to some IO sources used in the API;
|
||||
/// see [AppServer::api_manager]
|
||||
#[cfg(feature = "experiment_api")]
|
||||
MioManager(crate::api::mio::MioManagerIoSource),
|
||||
}
|
||||
|
||||
/// Number of epoll(7) events Rosenpass can receive at a time
|
||||
const EVENT_CAPACITY: usize = 20;
|
||||
|
||||
/// Holds the state of the application, namely the external IO
|
||||
/// This holds pretty much all of the state of the Rosenpass application
|
||||
/// including the cryptographic state in [Self::crypto_site]
|
||||
///
|
||||
/// Responsible for file IO, network IO
|
||||
// TODO add user control via unix domain socket and stdin/stdout
|
||||
/// Responsible for file IO, network IO, etc…
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/app_server_example.rs")]
|
||||
#[doc = "```"]
|
||||
#[derive(Debug)]
|
||||
pub struct AppServer {
|
||||
/// Contains the actual cryptographic implementation.
|
||||
///
|
||||
/// Because the API supports initializing the server with a keypair
|
||||
/// and CryptoServer needs to be initialized with a keypair, the struct
|
||||
/// struct is wrapped in a ConstructionSite
|
||||
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
|
||||
/// The UDP sockets used to send and receive protocol messages
|
||||
pub sockets: Vec<mio::net::UdpSocket>,
|
||||
/// Buffer for [mio] (epoll(7), async IO handling) IO events
|
||||
pub events: mio::Events,
|
||||
/// Supplemental buffer for [mio] events. See the inline documentation of [AppServer::try_recv]
|
||||
/// for details.
|
||||
pub short_poll_queue: VecDeque<mio::event::Event>,
|
||||
/// We have two different polling modes; long polling (legacy) and short polling (new and
|
||||
/// somewhat experimental). See [AppServer::try_recv] for details
|
||||
pub performed_long_poll: bool,
|
||||
/// Events produced by [mio] refer to IO sources by a numeric token assigned to them
|
||||
/// (see [MioTokenDispenser]). This index associateds mio token with the specific IO
|
||||
/// source stored in this object
|
||||
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
|
||||
/// Asynchronous IO source
|
||||
pub mio_poll: mio::Poll,
|
||||
/// MIO associates IO sources with numeric tokens. This struct takes care of generating these
|
||||
/// tokens
|
||||
pub mio_token_dispenser: MioTokenDispenser,
|
||||
/// Helpers handling communication with WireGuard; these take a generated key and forward it to
|
||||
/// WireGuard
|
||||
pub brokers: BrokerStore,
|
||||
/// This is our view of the peers; generally every peer in here is associated with one peer in
|
||||
/// CryptoServer
|
||||
pub peers: Vec<AppPeer>,
|
||||
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||
/// at the info log level
|
||||
pub verbosity: Verbosity,
|
||||
/// Used by [AppServer::try_recv] to ensure that all packages have been read
|
||||
/// from the UDP sockets
|
||||
pub all_sockets_drained: bool,
|
||||
/// Whether network message handling determined that a Denial of Service attack is happening
|
||||
pub under_load: DoSOperation,
|
||||
/// State kept by the [AppServer::try_recv] for polling
|
||||
pub blocking_polls_count: usize,
|
||||
/// State kept by the [AppServer::try_recv] for polling
|
||||
pub non_blocking_polls_count: usize,
|
||||
/// State kept by the [AppServer::try_recv] for polling
|
||||
pub unpolled_count: usize,
|
||||
/// State kept by the [AppServer::try_recv] for polling
|
||||
pub last_update_time: Instant,
|
||||
/// Used by integration tests to force [Self] into DoS condition
|
||||
/// and to terminate the AppServer after the test is complete
|
||||
pub test_helpers: Option<AppServerTest>,
|
||||
/// Helper for integration tests running rosenpass as a subprocess
|
||||
/// to terminate properly upon receiving an appropriate system signal.
|
||||
///
|
||||
/// This is primarily needed for coverage testing, since llvm-cov does not
|
||||
/// write coverage reports to disk when a process is stopped by the default
|
||||
/// signal handler.
|
||||
///
|
||||
/// See <https://github.com/rosenpass/rosenpass/issues/385>
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
pub term_signal: terminate::TerminateRequested,
|
||||
#[cfg(feature = "experiment_api")]
|
||||
/// The Rosenpass unix socket API handler; this is an experimental
|
||||
/// feature that can be used to embed Rosenpass in external applications
|
||||
/// via communication by unix socket
|
||||
pub api_manager: crate::api::mio::MioManager,
|
||||
}
|
||||
|
||||
@@ -199,14 +405,19 @@ pub struct AppServer {
|
||||
pub struct SocketPtr(pub usize);
|
||||
|
||||
impl SocketPtr {
|
||||
/// Retrieve the concrete udp socket associated with the pointer
|
||||
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
|
||||
&srv.sockets[self.0]
|
||||
}
|
||||
|
||||
/// Retrieve the concrete udp socket associated with the pointer, mutably
|
||||
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
|
||||
&mut srv.sockets[self.0]
|
||||
}
|
||||
|
||||
/// Send a UDP packet to another address.
|
||||
///
|
||||
/// Merely forwards to [mio::net::UdpSocket::send_to]
|
||||
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
||||
self.get(srv).send_to(buf, addr)?;
|
||||
Ok(())
|
||||
@@ -214,28 +425,41 @@ impl SocketPtr {
|
||||
}
|
||||
|
||||
/// Index based pointer to a Peer
|
||||
///
|
||||
/// This allows retrieving both the io-oriented and the cryptographic information
|
||||
/// about a peer.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AppPeerPtr(pub usize);
|
||||
|
||||
impl AppPeerPtr {
|
||||
/// Takes an index based handle and returns the actual peer
|
||||
/// Takes an pointer from the cryptography subsystem
|
||||
/// in [AppServer::crypto_site] and derives the associated AppPeerPtr
|
||||
/// in [AppServer]
|
||||
pub fn lift(p: PeerPtr) -> Self {
|
||||
Self(p.0)
|
||||
}
|
||||
|
||||
/// Returns an index based handle to one Peer
|
||||
/// Turns this pointer into a cryptographic peer pointer for [CryptoServer]
|
||||
/// in [AppServer::crypto_site]
|
||||
pub fn lower(&self) -> PeerPtr {
|
||||
PeerPtr(self.0)
|
||||
}
|
||||
|
||||
/// Retrieve the [AppPeer] pointed to by [Self]
|
||||
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
||||
&srv.peers[self.0]
|
||||
}
|
||||
|
||||
/// Retrieve the [AppPeer] pointed to by [Self], mutably
|
||||
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
||||
&mut srv.peers[self.0]
|
||||
}
|
||||
|
||||
/// Use the associated WireGuard PSK broker via [BrokerStorePtr]
|
||||
/// to upload a new PSK.
|
||||
///
|
||||
/// If no PSK broker is set and [AppPeer::outfile] is none, then
|
||||
/// this prints a warning
|
||||
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
|
||||
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
|
||||
let config = broker.peer_cfg.create_config(psk);
|
||||
@@ -248,17 +472,34 @@ impl AppPeerPtr {
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of [AppServer::poll].
|
||||
///
|
||||
/// Instructs [AppServer::event_loop_without_error_handling] on how to proceed.
|
||||
#[derive(Debug)]
|
||||
pub enum AppPollResult {
|
||||
/// Erase the key for a given peer. Corresponds to [crate::protocol::PollResult::DeleteKey]
|
||||
DeleteKey(AppPeerPtr),
|
||||
/// Send an initiation to the given peer. Corresponds to [crate::protocol::PollResult::SendInitiation]
|
||||
SendInitiation(AppPeerPtr),
|
||||
/// Send a retransmission to the given peer. Corresponds to
|
||||
/// [crate::protocol::PollResult::SendRetransmission]
|
||||
SendRetransmission(AppPeerPtr),
|
||||
/// Received a network message.
|
||||
///
|
||||
/// This is the only case without a correspondence in [crate::protocol::PollResult]
|
||||
ReceivedMessage(usize, Endpoint),
|
||||
}
|
||||
|
||||
/// The reason why we are outputting a key
|
||||
#[derive(Debug)]
|
||||
pub enum KeyOutputReason {
|
||||
/// The reason is that a new key for the given peer was successfully exchanged
|
||||
Exchanged,
|
||||
/// The reason is, that no key could be exchanged with the peer before the output
|
||||
/// key lifetime was reached; a [AppPollResult::DeleteKey] event was issued.
|
||||
///
|
||||
/// The key we output in this case is chosen randomly and serves to securely
|
||||
/// erase whatever key is currently stored.
|
||||
Stale,
|
||||
}
|
||||
|
||||
@@ -297,14 +538,21 @@ impl std::fmt::Display for Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
/// A network address bound to a particular socket.
|
||||
///
|
||||
/// We need the information which socket is used because different listen sockets
|
||||
/// might be on different networks.
|
||||
#[derive(Debug)]
|
||||
pub struct SocketBoundEndpoint {
|
||||
/// The socket the address can be reached under; this is generally
|
||||
/// determined when we actually receive an RespHello message
|
||||
socket: SocketPtr,
|
||||
/// Just the address
|
||||
/// The network address
|
||||
addr: SocketAddr,
|
||||
/// identifier
|
||||
/// Byte representation of this socket bound network address.
|
||||
/// Generated through [SocketBoundEndpoint::to_bytes].
|
||||
///
|
||||
/// Read through [HostIdentification::encode]
|
||||
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
|
||||
}
|
||||
|
||||
@@ -315,16 +563,22 @@ impl std::fmt::Display for SocketBoundEndpoint {
|
||||
}
|
||||
|
||||
impl SocketBoundEndpoint {
|
||||
/// Length in bytes of the serialized socket index
|
||||
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
|
||||
/// Length in bytes of the serialized ipv6 address
|
||||
const IPV6_SIZE: usize = 16;
|
||||
/// Length in bytes of the serialized port
|
||||
const PORT_SIZE: usize = 2;
|
||||
/// Length in bytes of the serialized ipv6 address scope (see [SocketAddrV6::scope_id])
|
||||
const SCOPE_ID_SIZE: usize = 4;
|
||||
|
||||
/// Length in size of
|
||||
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
|
||||
+ SocketBoundEndpoint::IPV6_SIZE
|
||||
+ SocketBoundEndpoint::PORT_SIZE
|
||||
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
|
||||
|
||||
/// Produce a new [Self]
|
||||
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
|
||||
let bytes = Self::to_bytes(&socket, &addr);
|
||||
Self {
|
||||
@@ -334,6 +588,7 @@ impl SocketBoundEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes [HostIdentification::encode] for [Self]. Value cached in [Self::bytes].
|
||||
fn to_bytes(
|
||||
socket: &SocketPtr,
|
||||
addr: &SocketAddr,
|
||||
@@ -368,12 +623,18 @@ impl HostIdentification for SocketBoundEndpoint {
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Start discovery from some addresses
|
||||
/// Given a list of potential network addresses, start peer discovery.
|
||||
///
|
||||
/// Will send initiations to different addresses given here on each [crate::msgs::InitHello]
|
||||
/// retransmission during the peer discovery phase.
|
||||
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
|
||||
}
|
||||
|
||||
/// Start endpoint discovery from a hostname
|
||||
/// Given a hostname, start peer discovery.
|
||||
///
|
||||
/// Will send initiations to different addresses assigned to the host name
|
||||
/// on each [crate::msgs::InitHello] retransmission during the peer discovery phase.
|
||||
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
|
||||
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
|
||||
Ok(Endpoint::Discovery(host))
|
||||
@@ -404,6 +665,8 @@ impl Endpoint {
|
||||
Some(Self::discovery_from_addresses(addrs))
|
||||
}
|
||||
|
||||
/// Send a message to the address referenced by this endpoint or to one of
|
||||
/// the endpoints if we are in the peer discovery phase for this endpoint
|
||||
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
|
||||
use Endpoint::*;
|
||||
match self {
|
||||
@@ -412,6 +675,9 @@ impl Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
/// List of addresses this endpoint may be associated with.
|
||||
///
|
||||
/// During peer discovery, this can be multiple addresses.
|
||||
fn addresses(&self) -> &[SocketAddr] {
|
||||
use Endpoint::*;
|
||||
match self {
|
||||
@@ -449,7 +715,14 @@ impl Endpoint {
|
||||
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
|
||||
#[derive(Debug)]
|
||||
pub struct HostPathDiscoveryEndpoint {
|
||||
scouting_state: Cell<(usize, usize)>, // addr_off, sock_off
|
||||
/// Round robin index the next [Self::send_scouting] call should send packets to
|
||||
///
|
||||
/// (address offset, socket offset)
|
||||
///
|
||||
/// Including the socket here accounts for the fact that some network addresses may be
|
||||
/// reachable only through particular UDP sockets
|
||||
scouting_state: Cell<(usize, usize)>,
|
||||
/// List of addresses fir oeer discovery
|
||||
addresses: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
@@ -460,6 +733,7 @@ impl std::fmt::Display for HostPathDiscoveryEndpoint {
|
||||
}
|
||||
|
||||
impl HostPathDiscoveryEndpoint {
|
||||
/// Initiate a peer discovery process through a list of potential addresses
|
||||
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
|
||||
let scouting_state = Cell::new((0, 0));
|
||||
Self {
|
||||
@@ -468,7 +742,7 @@ impl HostPathDiscoveryEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup a hostname
|
||||
/// Initiate a peer discovery process through hostname lookup
|
||||
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
|
||||
@@ -476,10 +750,14 @@ impl HostPathDiscoveryEndpoint {
|
||||
})
|
||||
}
|
||||
|
||||
/// List of address candidates for the peer
|
||||
pub fn addresses(&self) -> &Vec<SocketAddr> {
|
||||
&self.addresses
|
||||
}
|
||||
|
||||
/// Calculates and stores the next value for [Self::scouting_state]
|
||||
/// given the address and socket we just sent a scouting [crate::msgs::InitHello] message
|
||||
/// to
|
||||
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
|
||||
self.scouting_state.set((
|
||||
(addr_no + 1) % self.addresses.len(),
|
||||
@@ -534,6 +812,11 @@ impl HostPathDiscoveryEndpoint {
|
||||
}
|
||||
|
||||
impl AppServer {
|
||||
/// Construct a new AppServer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self].
|
||||
pub fn new(
|
||||
keypair: Option<(SSk, SPk)>,
|
||||
addrs: Vec<SocketAddr>,
|
||||
@@ -633,6 +916,8 @@ impl AppServer {
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
|
||||
term_signal: terminate::TerminateRequested::new()?,
|
||||
crypto_site,
|
||||
peers: Vec::new(),
|
||||
verbosity,
|
||||
@@ -656,22 +941,37 @@ impl AppServer {
|
||||
})
|
||||
}
|
||||
|
||||
/// Access the cryptographic protocol server
|
||||
///
|
||||
/// This may return an error if [Self] was initialized without a keypair
|
||||
/// and no keypair has been supplied since then.
|
||||
///
|
||||
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
|
||||
self.crypto_site
|
||||
.product_ref()
|
||||
.context("Cryptography handler not initialized")
|
||||
}
|
||||
|
||||
/// Access the cryptographic protocol server, mutably
|
||||
///
|
||||
/// This may return an error if [Self] was initialized without a keypair
|
||||
/// and no keypair has been supplied since then.
|
||||
///
|
||||
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
|
||||
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
|
||||
self.crypto_site
|
||||
.product_mut()
|
||||
.context("Cryptography handler not initialized")
|
||||
}
|
||||
|
||||
/// If set to [Verbosity::Verbose], then some extra information will be printed
|
||||
/// at the info log level
|
||||
pub fn verbose(&self) -> bool {
|
||||
matches!(self.verbosity, Verbosity::Verbose)
|
||||
}
|
||||
|
||||
/// Used by [Self::new] to register a new udp listen source
|
||||
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
|
||||
let mio_token = self.mio_token_dispenser.dispense();
|
||||
self.mio_poll
|
||||
@@ -683,16 +983,19 @@ impl AppServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used to register a source of IO such as a listen socket with [Self::io_source_index]
|
||||
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
|
||||
let prev = self.io_source_index.insert(token, io_source);
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
/// Unregister an IO source registered with [Self::register_io_source]
|
||||
pub fn unregister_io_source(&mut self, token: mio::Token) {
|
||||
let value = self.io_source_index.remove(&token);
|
||||
assert!(value.is_some(), "Removed IO source that does not exist");
|
||||
}
|
||||
|
||||
/// Register a new WireGuard PSK broker
|
||||
pub fn register_broker(
|
||||
&mut self,
|
||||
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
|
||||
@@ -715,6 +1018,7 @@ impl AppServer {
|
||||
Ok(BrokerStorePtr(ptr))
|
||||
}
|
||||
|
||||
/// Unregister a WireGuard PSK broker registered with [Self::register_broker]
|
||||
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
|
||||
let mut broker = self
|
||||
.brokers
|
||||
@@ -726,6 +1030,11 @@ impl AppServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a new protocol peer
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::new].
|
||||
pub fn add_peer(
|
||||
&mut self,
|
||||
psk: Option<SymKey>,
|
||||
@@ -754,18 +1063,40 @@ impl AppServer {
|
||||
Ok(AppPeerPtr(pn))
|
||||
}
|
||||
|
||||
pub fn listen_loop(&mut self) -> anyhow::Result<()> {
|
||||
/// Main IO handler; this generally does not terminate
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::new].
|
||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||
const INIT_SLEEP: f64 = 0.01;
|
||||
const MAX_FAILURES: i32 = 10;
|
||||
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 +1121,10 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||
/// IO handler without proactive restarts after errors.
|
||||
///
|
||||
/// This is used internally in [Self::event_loop].
|
||||
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
|
||||
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
||||
|
||||
/// if socket address for peer is known, call closure
|
||||
@@ -909,6 +1243,8 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for [Self::event_loop_without_error_handling] to handle network messages
|
||||
/// under DoS condition
|
||||
fn handle_msg_under_load(
|
||||
&mut self,
|
||||
endpoint: &Endpoint,
|
||||
@@ -925,6 +1261,8 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used as a helper by [Self::event_loop_without_error_handling] when
|
||||
/// a new output key has been echanged
|
||||
pub fn output_key(
|
||||
&mut self,
|
||||
peer: AppPeerPtr,
|
||||
@@ -974,6 +1312,10 @@ impl AppServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Poll for events from the cryptographic server ([Self::crypto_server()])
|
||||
/// and for IO events through [Self::poll].
|
||||
///
|
||||
/// Used internally in [Self::event_loop_without_error_handling]
|
||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||
use crate::protocol::PollResult as C;
|
||||
use AppPollResult as A;
|
||||
@@ -1005,7 +1347,9 @@ impl AppServer {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Tries to receive a new message
|
||||
/// Tries to receive a new message from the network sockets.
|
||||
///
|
||||
/// Used internally in [Self::poll]
|
||||
///
|
||||
/// - might wait for an duration up to `timeout`
|
||||
/// - returns immediately if an error occurs
|
||||
@@ -1177,6 +1521,7 @@ impl AppServer {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Internal helper for [Self::try_recv]
|
||||
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
|
||||
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||
// Fill the short poll buffer with the acquired events
|
||||
@@ -1187,6 +1532,7 @@ impl AppServer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Internal helper for [Self::try_recv]
|
||||
fn try_recv_from_mio_token(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
@@ -1203,6 +1549,7 @@ impl AppServer {
|
||||
self.try_recv_from_io_source(buf, io_source)
|
||||
}
|
||||
|
||||
/// Internal helper for [Self::try_recv]
|
||||
fn try_recv_from_io_source(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
@@ -1233,6 +1580,7 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal helper for [Self::try_recv]
|
||||
fn try_recv_from_listen_socket(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
@@ -1268,6 +1616,20 @@ impl AppServer {
|
||||
}
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
/// This is a wrapper around a reference to [AppServer] that
|
||||
/// dishes out references to [AppServer::api_manager] and
|
||||
/// to [AppServer] itself.
|
||||
///
|
||||
/// It really just implements [crate::api::mio::MioManagerContext] which
|
||||
/// provides the methods operating on [crate::api::mio::MioManager] provided
|
||||
/// through a trait.
|
||||
///
|
||||
/// This is a rather complicated way of providing just a few functions. The entire
|
||||
/// point of this exercise is to decouple the code in the API from [AppServer] and
|
||||
/// this file a bit, despite those functions all needing access to [AppServer].
|
||||
///
|
||||
/// We want the code to live in its own module instead of expanding and expanding the source
|
||||
/// file with [AppServer] more and more.
|
||||
struct MioManagerFocus<'a>(&'a mut AppServer);
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
@@ -1288,3 +1650,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Contains the code used to parse command line parameters for rosenpass.
|
||||
//!
|
||||
//! [CliArgs::run] is called by the rosenpass main function and contains the
|
||||
//! bulk of our boostrapping code while the main function just sets up the basic environment
|
||||
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use clap::{Parser, Subcommand};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
@@ -31,15 +36,25 @@ use {
|
||||
std::thread,
|
||||
};
|
||||
|
||||
/// enum representing a choice of interface to a WireGuard broker
|
||||
/// How to reach a WireGuard PSK Broker
|
||||
#[derive(Debug)]
|
||||
pub enum BrokerInterface {
|
||||
/// The PSK Broker is listening on a unix socket at the given path
|
||||
Socket(PathBuf),
|
||||
/// The PSK Broker broker is already connected to this process; a
|
||||
/// unix socket stream can be reached at the given file descriptor.
|
||||
///
|
||||
/// This is generally used with file descriptor passing.
|
||||
FileDescriptor(i32),
|
||||
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
|
||||
/// and hand one end of the socketpair to the broker process via file
|
||||
/// descriptor passing to the subprocess
|
||||
SocketPair,
|
||||
}
|
||||
|
||||
/// struct holding all CLI arguments for `clap` crate to parse
|
||||
/// Command line arguments to the Rosenpass binary.
|
||||
///
|
||||
/// Used for parsing with [clap].
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||
pub struct CliArgs {
|
||||
@@ -80,6 +95,7 @@ pub struct CliArgs {
|
||||
#[arg(short, long, group = "psk-broker-specs")]
|
||||
psk_broker_spawn: bool,
|
||||
|
||||
/// The subcommand to be invoked
|
||||
#[command(subcommand)]
|
||||
pub command: Option<CliCommand>,
|
||||
|
||||
@@ -98,6 +114,10 @@ pub struct CliArgs {
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
/// Apply the command line parameters to the Rosenpass configuration struct
|
||||
///
|
||||
/// Generally the flow of control here is that all the command line parameters
|
||||
/// are merged into the configuration file to avoid much code duplication.
|
||||
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_config(_cfg)?;
|
||||
@@ -123,9 +143,11 @@ impl CliArgs {
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
if let Some(path_ref) = self.psk_broker_path.as_ref() {
|
||||
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
|
||||
@@ -138,9 +160,10 @@ impl CliArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the WireGuard PSK broker interface configured.
|
||||
///
|
||||
/// Returns `None` if the `experiment_api` feature is disabled.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
/// returns the broker interface set by CLI args
|
||||
/// returns `None` if the `experiment_api` feature isn't enabled
|
||||
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
|
||||
None
|
||||
}
|
||||
@@ -244,15 +267,17 @@ pub enum CliCommand {
|
||||
}
|
||||
|
||||
impl CliArgs {
|
||||
/// Runs the command specified via CLI
|
||||
/// Run Rosenpass with the given command line parameters
|
||||
///
|
||||
/// ## TODO
|
||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
/// This contains the bulk of our startup logic with
|
||||
/// the main function just setting up the basic environment
|
||||
/// and then calling this function.
|
||||
pub fn run(
|
||||
self,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
test_helpers: Option<AppServerTest>,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||
use CliCommand::*;
|
||||
match &self.command {
|
||||
Some(GenConfig { config_file, force }) => {
|
||||
@@ -403,6 +428,7 @@ impl CliArgs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used by [Self::run] to start the Rosenpass key exchange server
|
||||
fn event_loop(
|
||||
config: config::Rosenpass,
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
@@ -470,6 +496,19 @@ impl CliArgs {
|
||||
srv.event_loop()
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn create_broker(
|
||||
broker_interface: Option<BrokerInterface>,
|
||||
@@ -485,6 +524,19 @@ impl CliArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the WireGuard PSK broker to be used by
|
||||
/// [crate::app_server::AppServer].
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is set, then this communicates with a PSK broker
|
||||
/// running in a different process as configured via
|
||||
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
///
|
||||
/// If the `experiment_api`
|
||||
/// feature flag is not set, then this returns a [NativeUnixBroker],
|
||||
/// sending pre-shared keys directly to WireGuard from within this
|
||||
/// process.
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
fn create_broker(
|
||||
_broker_interface: Option<BrokerInterface>,
|
||||
@@ -492,6 +544,10 @@ impl CliArgs {
|
||||
Ok(Box::new(NativeUnixBroker::new()))
|
||||
}
|
||||
|
||||
/// Used by [Self::create_broker] if the `experiment_api` is configured
|
||||
/// to set up the connection with the PSK broker process as configured
|
||||
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
|
||||
/// fields.
|
||||
#[cfg(feature = "experiment_api")]
|
||||
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
|
||||
// Connect to the psk broker unix socket if one was specified
|
||||
@@ -549,7 +605,7 @@ impl CliArgs {
|
||||
}
|
||||
|
||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
//! [`Rosenpass`] which holds such a configuration.
|
||||
//!
|
||||
//! ## TODO
|
||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||
|
||||
use crate::protocol::{SPk, SSk};
|
||||
use rosenpass_util::file::LoadValue;
|
||||
use std::{
|
||||
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
/// Configuration for the Rosenpass key exchange
|
||||
///
|
||||
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Rosenpass {
|
||||
// TODO: Raise error if secret key or public key alone is set during deserialization
|
||||
// SEE: https://github.com/serde-rs/serde/issues/2793
|
||||
@@ -46,7 +50,10 @@ pub struct Rosenpass {
|
||||
/// list of [`SocketAddr`] to listen on
|
||||
///
|
||||
/// Examples:
|
||||
/// - `0.0.0.0:123`
|
||||
///
|
||||
/// - `0.0.0.0:123` – Listen on any interface using IPv4, port 123
|
||||
/// - `[::1]:1234` – Listen on IPv6 localhost, port 1234
|
||||
/// - `[::]:4476` – Listen on any IPv4 or IPv6 interface, port 4476
|
||||
pub listen: Vec<SocketAddr>,
|
||||
|
||||
/// log verbosity
|
||||
@@ -68,6 +75,7 @@ pub struct Rosenpass {
|
||||
pub config_file_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Public key and secret key locations.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
pub struct Keypair {
|
||||
/// path to the public key file
|
||||
@@ -78,6 +86,7 @@ pub struct Keypair {
|
||||
}
|
||||
|
||||
impl Keypair {
|
||||
/// Construct a keypair from its fields
|
||||
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
|
||||
let public_key = public_key.as_ref().to_path_buf();
|
||||
let secret_key = secret_key.as_ref().to_path_buf();
|
||||
@@ -88,62 +97,72 @@ impl Keypair {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
/// Level of verbosity for [crate::app_server::AppServer]
|
||||
///
|
||||
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
|
||||
/// for details.
|
||||
///
|
||||
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum Verbosity {
|
||||
Quiet,
|
||||
Verbose,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - examples
|
||||
/// - documentation
|
||||
/// Configuration data for a single Rosenpass peer
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RosenpassPeer {
|
||||
/// path to the public key of the peer
|
||||
pub public_key: PathBuf,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// The hostname and port to connect to
|
||||
///
|
||||
/// Can be a
|
||||
///
|
||||
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
|
||||
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
|
||||
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
|
||||
pub endpoint: Option<String>,
|
||||
|
||||
/// path to the pre-shared key with the peer
|
||||
/// path to the pre-shared key shared with the peer
|
||||
///
|
||||
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
|
||||
pub pre_shared_key: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
|
||||
/// to the given file and write a notification to standard out to let the calling application
|
||||
/// know that a new key was exchanged
|
||||
#[serde(default)]
|
||||
pub key_out: Option<PathBuf>,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[serde(flatten)]
|
||||
pub wg: Option<WireGuard>,
|
||||
}
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Information for supplying exchanged keys directly to WireGuard
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct WireGuard {
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
|
||||
/// key exchange
|
||||
pub device: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// WireGuard public key of the peer to supply with pre-shared keys
|
||||
pub peer: String,
|
||||
|
||||
/// ## TODO
|
||||
/// - documentation
|
||||
/// Extra parameters passed to the `wg` command
|
||||
#[serde(default)]
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Rosenpass {
|
||||
/// Generate an empty configuration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
@@ -156,8 +175,15 @@ impl Rosenpass {
|
||||
/// checked whether they even exist.
|
||||
///
|
||||
/// ## TODO
|
||||
///
|
||||
/// - consider using a different algorithm to determine home directory – the below one may
|
||||
/// behave unexpectedly on Windows
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||
// read file and deserialize
|
||||
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||
@@ -185,7 +211,13 @@ impl Rosenpass {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Write a config to a file
|
||||
/// Encode a configuration object as toml and write it to a file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||
let serialized_config =
|
||||
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||
@@ -194,6 +226,12 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Commit the configuration to where it came from, overwriting the original file
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn commit(&self) -> anyhow::Result<()> {
|
||||
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
|
||||
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||
@@ -201,13 +239,21 @@ impl Rosenpass {
|
||||
self.store(&self.config_file_path)
|
||||
}
|
||||
|
||||
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
|
||||
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "experiment_api")]
|
||||
self.api.apply_to_app_server(_srv)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a configuration
|
||||
/// Check that the configuration is sound, ensuring
|
||||
/// for instance that the referenced files exist
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
if let Some(ref keypair) = self.keypair {
|
||||
// check the public key file exists
|
||||
@@ -284,6 +330,21 @@ impl Rosenpass {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
|
||||
/// and the configuration values.
|
||||
///
|
||||
/// This was introduced when we introduced a unix-socket API feature allowing the server
|
||||
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
|
||||
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
|
||||
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
|
||||
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
|
||||
/// enabled in the configuration.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn check_usefullness(&self) -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "experiment_api"))]
|
||||
ensure!(self.keypair.is_some(), "Server keypair missing.");
|
||||
@@ -299,15 +360,38 @@ impl Rosenpass {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Produce an empty confuguration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
|
||||
/// Produce configuration from the keypair
|
||||
///
|
||||
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
|
||||
Self::new(Some(Keypair::new(pk, sk)))
|
||||
}
|
||||
|
||||
/// Creates a new configuration
|
||||
/// Initialize a minimal configuration with the [Self::keypair] field supplied
|
||||
/// as a parameter
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn new(keypair: Option<Keypair>) -> Self {
|
||||
Self {
|
||||
keypair,
|
||||
@@ -321,6 +405,14 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
||||
///
|
||||
/// I.e. listen on any interface.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn add_if_any(&mut self, port: u16) {
|
||||
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||
@@ -333,8 +425,20 @@ impl Rosenpass {
|
||||
self.listen.push(ipv6_any);
|
||||
}
|
||||
|
||||
/// from chaotic args
|
||||
/// Quest: the grammar is undecideable, what do we do here?
|
||||
/// Parser for the old, IP style grammar.
|
||||
///
|
||||
/// See out manual page rosenpass-exchange(1) on details about the grammar.
|
||||
///
|
||||
/// This function parses the grammar and turns it into an instance of the configuration
|
||||
/// struct.
|
||||
///
|
||||
/// TODO: the grammar is undecidable, what do we do here?
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[doc = "```ignore"]
|
||||
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
|
||||
#[doc = "```"]
|
||||
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||
let mut config = Self::new(Some(Keypair::new("", "")));
|
||||
|
||||
@@ -525,11 +629,13 @@ impl Rosenpass {
|
||||
}
|
||||
|
||||
impl Default for Verbosity {
|
||||
/// Self::Quiet
|
||||
fn default() -> Self {
|
||||
Self::Quiet
|
||||
}
|
||||
}
|
||||
|
||||
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
|
||||
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||
secret_key = "/path/to/rp-secret-key"
|
||||
listen = []
|
||||
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use std::{borrow::Borrow, net::IpAddr};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
|
||||
toml::from_str(s.borrow())
|
||||
@@ -664,37 +770,6 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_cli_parse() {
|
||||
let args = split_str(
|
||||
"public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out",
|
||||
);
|
||||
|
||||
let config = Rosenpass::parse_args(args).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_parse_multiple_peers() {
|
||||
let args = split_str(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,29 +8,99 @@ use thiserror::Error;
|
||||
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A pair of matching public/secret keys used to launch the crypto server.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Decomposing a key pair into its individual components, then recreating it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::Keypair;
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let random_pair = Keypair::random();
|
||||
/// let random_copy = random_pair.clone();
|
||||
/// let (sk_copy, pk_copy) = random_copy.into_parts();
|
||||
///
|
||||
/// // Re-assemble the key pair from the original secret/public key
|
||||
/// // Note that it doesn't have to be the exact same keys;
|
||||
/// // you could just as easily use a completely different pair here
|
||||
/// let reconstructed_pair = Keypair::from_parts((sk_copy, pk_copy));
|
||||
///
|
||||
/// assert_eq!(random_pair.sk.secret(), reconstructed_pair.sk.secret());
|
||||
/// assert_eq!(random_pair.pk, reconstructed_pair.pk);
|
||||
/// ```
|
||||
pub struct Keypair {
|
||||
/// Secret key matching the crypto server's public key.
|
||||
pub sk: SSk,
|
||||
/// Public key identifying the crypto server instance.
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
// TODO: We need a named tuple derive
|
||||
impl Keypair {
|
||||
/// Creates a new key pair from the given secret/public key components.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let random_sk = SSk::random();
|
||||
/// let random_pk = SPk::random();
|
||||
/// let random_pair = Keypair::new(random_sk.clone(), random_pk.clone());
|
||||
///
|
||||
/// assert_eq!(random_sk.secret(), random_pair.sk.secret());
|
||||
/// assert_eq!(random_pk, random_pair.pk);
|
||||
/// ```
|
||||
pub fn new(sk: SSk, pk: SPk) -> Self {
|
||||
Self { sk, pk }
|
||||
}
|
||||
|
||||
/// Creates a new "empty" key pair. All bytes are initialized to zero.
|
||||
///
|
||||
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass::protocol::{Keypair, SSk, SPk};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let zero_sk = SSk::zero();
|
||||
/// let zero_pk = SPk::zero();
|
||||
/// let zero_pair = Keypair::zero();
|
||||
///
|
||||
/// assert_eq!(zero_sk.secret(), zero_pair.sk.secret());
|
||||
/// assert_eq!(zero_pk, zero_pair.pk);
|
||||
/// ```
|
||||
pub fn zero() -> Self {
|
||||
Self::new(SSk::zero(), SPk::zero())
|
||||
}
|
||||
|
||||
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
|
||||
///
|
||||
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
|
||||
pub fn random() -> Self {
|
||||
Self::new(SSk::random(), SPk::random())
|
||||
}
|
||||
|
||||
/// Creates a new key pair from the given public/secret key components.
|
||||
pub fn from_parts(parts: (SSk, SPk)) -> Self {
|
||||
Self::new(parts.0, parts.1)
|
||||
}
|
||||
|
||||
/// Deconstructs the key pair, yielding the individual public/secret key components.
|
||||
pub fn into_parts(self) -> (SSk, SPk) {
|
||||
(self.sk, self.pk)
|
||||
}
|
||||
@@ -38,25 +108,77 @@ impl Keypair {
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("PSK already set in BuildCryptoServer")]
|
||||
/// Error indicating that the PSK is already set.
|
||||
/// Unused in the current version of the protocol.
|
||||
pub struct PskAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Keypair already set in BuildCryptoServer")]
|
||||
/// Error type indicating that the public/secret key pair has already been set.
|
||||
pub struct KeypairAlreadySet;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Can not construct CryptoServer: Missing keypair")]
|
||||
/// Error type indicating that no public/secret key pair has been provided.
|
||||
pub struct MissingKeypair;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// Builder for setting up a [CryptoServer] (with deferred initialization).
|
||||
///
|
||||
/// There are multiple ways of creating a crypto server:
|
||||
///
|
||||
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
|
||||
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
|
||||
///
|
||||
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
|
||||
/// This may be useful when they depend on runtime conditions or have to be fetched asynchronously.
|
||||
/// It's possible to use the builder multiple times; it then serves as a "blueprint" for new
|
||||
/// instances, several of which may be spawned with the same base configuration (or variations thereof).
|
||||
///
|
||||
/// Note that the server won't actually launch without a key pair (expect a [MissingKeypair] error).
|
||||
/// The setup will be much simplified if one is provided, at the cost of some flexibility.
|
||||
/// It's however possible to defer this step in case your application requires it.
|
||||
///
|
||||
/// For additional details or examples, see [AppServer::crypto_site][crate::app_server::AppServer::crypto_site] and [ConstructionSite][rosenpass_util::build::ConstructionSite].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// let keypair = Keypair::random();
|
||||
/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random() };
|
||||
/// let peer2 = PeerParams { psk: None, pk: SPk::random() };
|
||||
///
|
||||
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]);
|
||||
/// builder.add_peer(peer2.psk.clone(), peer2.pk);
|
||||
///
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.peers.len(), 2);
|
||||
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(server.spkm, keypair.pk);
|
||||
/// ```
|
||||
pub struct BuildCryptoServer {
|
||||
/// The key pair (secret/public key) identifying the crypto server instance.
|
||||
pub keypair: Option<Keypair>,
|
||||
/// A list of network peers that should be registered when launching the server.
|
||||
pub peers: Vec<PeerParams>,
|
||||
}
|
||||
|
||||
impl Build<CryptoServer> for BuildCryptoServer {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
/// Creates a crypto server, adding all peers that have previously been registered.
|
||||
///
|
||||
/// You must provide a key pair at the time of instantiation.
|
||||
/// If the list of peers is outdated, building the server will fail.
|
||||
///
|
||||
/// In this case, make sure to remove or re-add any peers that may have changed.
|
||||
fn build(self) -> Result<CryptoServer, Self::Error> {
|
||||
let Some(Keypair { sk, pk }) = self.keypair else {
|
||||
return Err(MissingKeypair)?;
|
||||
@@ -74,20 +196,32 @@ impl Build<CryptoServer> for BuildCryptoServer {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
|
||||
/// for a given session that is being managed by the crypto server.
|
||||
///
|
||||
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
|
||||
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
|
||||
/// can be provided when setting up the connection.
|
||||
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
|
||||
pub struct PeerParams {
|
||||
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
|
||||
pub psk: Option<SymKey>,
|
||||
/// Public key identifying the peer.
|
||||
pub pk: SPk,
|
||||
}
|
||||
|
||||
impl BuildCryptoServer {
|
||||
/// Creates a new builder instance using the given key pair and peer list.
|
||||
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
|
||||
Self { keypair, peers }
|
||||
}
|
||||
|
||||
/// Creates an "incomplete" builder instance, without assigning a key pair.
|
||||
pub fn empty() -> Self {
|
||||
Self::new(None, Vec::new())
|
||||
}
|
||||
|
||||
/// Creates a builder instance from the given key pair and peer list components.
|
||||
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
|
||||
Self {
|
||||
keypair: parts.0,
|
||||
@@ -95,32 +229,165 @@ impl BuildCryptoServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Deconstructs the current builder instance, taking ownership of its key pair and peer list.
|
||||
///
|
||||
/// Replaces all parameters with their default values, which allows extracting them
|
||||
/// while leaving the builder in a reusable state.
|
||||
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
(self.keypair.take(), self.peers.swap_with_default())
|
||||
}
|
||||
|
||||
/// Deconstructs the builder instance, yielding the assigned key pair and peer list.
|
||||
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
|
||||
self.take_parts()
|
||||
}
|
||||
|
||||
/// Creates a new builder instance, assigning the given keypair to it.
|
||||
///
|
||||
/// Note that only one key pair can be assigned (expect [KeypairAlreadySet] on failure).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Adding key pairs to an existing builder
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
|
||||
///
|
||||
/// // Deferred initialization: Create builder first, add the key pair later
|
||||
/// let mut builder = BuildCryptoServer::empty();
|
||||
/// // Do something with the builder ...
|
||||
///
|
||||
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||
/// // Now we've got a key pair that should be added to the configuration
|
||||
/// let keypair = Keypair::random();
|
||||
/// builder.with_keypair(keypair.clone()).expect("build with key pair failed");
|
||||
///
|
||||
/// // New server instances can now make use of the assigned key pair
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(server.spkm, keypair.pk);
|
||||
/// ```
|
||||
///
|
||||
/// ## Basic error handling: Re-assigning key pairs
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, KeypairAlreadySet};
|
||||
///
|
||||
/// // In this case, we'll create a functional builder from its various components
|
||||
/// // These could be salvaged from another builder, or obtained from disk/network (etc.)
|
||||
/// let keypair = Keypair::random();
|
||||
/// let mut builder = BuildCryptoServer::from_parts((Some(keypair.clone()), Vec::new()));
|
||||
///
|
||||
/// // The builder has already been assigned a key pair, so this won't work
|
||||
/// let err = builder.with_keypair(keypair).expect_err("should fail to reassign key pair");
|
||||
/// assert!(matches!(err, KeypairAlreadySet));
|
||||
/// ```
|
||||
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
|
||||
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
|
||||
self.keypair.insert(keypair).discard_result();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Creates a new builder instance, adding a new entry to the list of registered peers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Adding peers to an existing builder:
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||
///
|
||||
/// // Deferred initialization: Create builder first, add some peers later
|
||||
/// let keypair_option = Some(Keypair::random());
|
||||
/// let mut builder = BuildCryptoServer::new(keypair_option, Vec::new());
|
||||
/// assert!(builder.peers.is_empty());
|
||||
///
|
||||
/// // Do something with the builder ...
|
||||
///
|
||||
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
|
||||
/// // Now we've found a peer that should be added to the configuration
|
||||
/// let pre_shared_key = SymKey::random();
|
||||
/// let public_key = SPk::random();
|
||||
/// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone());
|
||||
///
|
||||
/// // New server instances will then start with the peer being registered already
|
||||
/// let server = builder.build().expect("build failed");
|
||||
/// assert_eq!(server.peers.len(), 1);
|
||||
/// let peer = &server.peers[0];
|
||||
/// let peer_psk = Some(peer.psk.clone()).expect("PSK is None");
|
||||
/// assert_eq!(peer.spkt, public_key);
|
||||
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
|
||||
/// ```
|
||||
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
|
||||
// TODO: Check here already whether peer was already added
|
||||
self.peers.push(PeerParams { psk, pk });
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a new entry to the list of registered peers, with or without a pre-shared key.
|
||||
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
|
||||
let id = PeerPtr(self.peers.len());
|
||||
self.with_added_peer(psk, pk);
|
||||
id
|
||||
}
|
||||
|
||||
/// Creates a new builder, taking ownership of another instance's key pair and peer list.
|
||||
/// Allows duplicating the current set of launch parameters, which can then be used to
|
||||
/// start multiple servers with the exact same configuration (or variants using it as a base).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Extracting the server configuration from a builder:
|
||||
///
|
||||
/// ```rust
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
///
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
|
||||
///
|
||||
/// let keypair = Keypair::random();
|
||||
/// let peer_pk = SPk::random();
|
||||
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]);
|
||||
/// builder.add_peer(None, peer_pk);
|
||||
///
|
||||
/// // Extract configuration parameters from the decomissioned builder
|
||||
/// let (keypair_option, peers) = builder.take_parts();
|
||||
/// let extracted_keypair = keypair_option.unwrap();
|
||||
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||
/// assert_eq!(peers.len(), 1);
|
||||
///
|
||||
/// // Now we can create a new builder with the same configuration
|
||||
/// let parts = (Some(extracted_keypair), peers);
|
||||
/// let mut reassembled_builder = BuildCryptoServer::from_parts(parts);
|
||||
/// let new_builder = reassembled_builder.emancipate();
|
||||
///
|
||||
/// // Do something with the new builder ...
|
||||
///
|
||||
/// // ... and now, deconstruct this one as well - still using the same parts
|
||||
/// let (keypair_option, peers) = new_builder.into_parts();
|
||||
/// let extracted_keypair = keypair_option.unwrap();
|
||||
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
|
||||
/// assert_eq!(extracted_keypair.pk, keypair.pk);
|
||||
/// assert_eq!(peers.len(), 1);
|
||||
/// ```
|
||||
pub fn emancipate(&mut self) -> Self {
|
||||
Self::from_parts(self.take_parts())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,17 @@ struct KillChild(std::process::Child);
|
||||
|
||||
impl Drop for KillChild {
|
||||
fn drop(&mut self) {
|
||||
self.0.kill().discard_result();
|
||||
self.0.wait().discard_result()
|
||||
use rustix::process::{kill_process, Pid, Signal::Term};
|
||||
let pid = Pid::from_child(&self.0);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
130
rosenpass/tests/app_server_example.rs
Normal file
130
rosenpass/tests/app_server_example.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
ops::DerefMut,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
thread::{self, sleep},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::ensure;
|
||||
use rosenpass::{
|
||||
app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE},
|
||||
protocol::{SPk, SSk, SymKey},
|
||||
};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
|
||||
|
||||
#[test]
|
||||
fn key_exchange_with_app_server() -> anyhow::Result<()> {
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
let outfile_a = tmpdir.path().join("osk_a");
|
||||
let outfile_b = tmpdir.path().join("osk_b");
|
||||
|
||||
// Set security policy for storing secrets; choose the one that is faster for testing
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
// Introduce the servers to each other
|
||||
let psk_a = SymKey::random();
|
||||
let psk_b = psk_a.clone();
|
||||
|
||||
let (tx_a, rx_b) = mpsc::sync_channel(1);
|
||||
let (tx_b, rx_a) = mpsc::sync_channel(1);
|
||||
|
||||
let (tx_term_a, rx_term_a) = mpsc::channel();
|
||||
let (tx_term_b, rx_term_b) = mpsc::channel();
|
||||
|
||||
let configs = [
|
||||
(false, outfile_a.clone(), psk_a, tx_a, rx_a, rx_term_a),
|
||||
(true, outfile_b.clone(), psk_b, tx_b, rx_b, rx_term_b),
|
||||
];
|
||||
|
||||
for (is_client, osk, psk, tx, rx, rx_term) in configs {
|
||||
thread::spawn(move || {
|
||||
run(move || -> anyhow::Result<()> {
|
||||
let mut srv = TestServer::new(rx_term)?;
|
||||
|
||||
tx.send((srv.loopback_port()?, srv.public_key()?.clone()))?;
|
||||
let (otr_port, otr_pk) = rx.recv()?;
|
||||
|
||||
let psk = Some(psk);
|
||||
let broker_peer = None;
|
||||
let pk = otr_pk;
|
||||
let outfile = Some(osk);
|
||||
let port = otr_port;
|
||||
let hostname = is_client.then(|| format!("[::1]:{port}"));
|
||||
srv.app_srv
|
||||
.add_peer(psk, pk, outfile, broker_peer, hostname)?;
|
||||
|
||||
srv.app_srv.event_loop()
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// Busy wait for both keys to be exchanged
|
||||
let mut successful_exchange = false;
|
||||
for _ in 0..2000 {
|
||||
// 40s
|
||||
sleep(Duration::from_millis(20));
|
||||
run(|| -> anyhow::Result<()> {
|
||||
let osk_a = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_a)?;
|
||||
let osk_b = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_b)?;
|
||||
successful_exchange = rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret());
|
||||
Ok(())
|
||||
})
|
||||
.discard_result();
|
||||
if successful_exchange {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the parties to terminate
|
||||
tx_term_a.send(())?;
|
||||
tx_term_b.send(())?;
|
||||
|
||||
assert!(
|
||||
successful_exchange,
|
||||
"Test did not complete successfully within the deadline"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TestServer {
|
||||
app_srv: AppServer,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
fn new(termination_queue: mpsc::Receiver<()>) -> anyhow::Result<Self> {
|
||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
|
||||
|
||||
let keypair = Some((sk, pk));
|
||||
let addrs = vec![
|
||||
SocketAddr::from_str("[::1]:0")?, // Localhost, any port. For connecting to the test server.
|
||||
// ipv4_any_binding(), // any IPv4 interface
|
||||
// ipv6_any_binding(), // any IPv6 interface
|
||||
];
|
||||
let verbosity = rosenpass::config::Verbosity::Verbose;
|
||||
let test_helpers = Some(AppServerTest {
|
||||
enable_dos_permanently: false,
|
||||
termination_handler: Some(termination_queue),
|
||||
});
|
||||
|
||||
let app_srv = AppServer::new(keypair, addrs, verbosity, test_helpers)?;
|
||||
|
||||
Self { app_srv }.ok()
|
||||
}
|
||||
|
||||
fn loopback_port(&self) -> anyhow::Result<u16> {
|
||||
self.app_srv.sockets[0].local_addr()?.port().ok()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> anyhow::Result<&SPk> {
|
||||
Ok(&self.app_srv.crypto_server()?.spkm)
|
||||
}
|
||||
}
|
||||
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
10
rosenpass/tests/config_Rosenpass_add_if_any.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use rosenpass::config::Rosenpass;
|
||||
|
||||
#[test]
|
||||
fn config_Rosenpass_add_if_any_example() {
|
||||
let mut v = Rosenpass::empty();
|
||||
v.add_if_any(4000);
|
||||
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
|
||||
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
|
||||
}
|
||||
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
18
rosenpass/tests/config_Rosenpass_new.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use rosenpass::config::{Keypair, Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_new() {
|
||||
let (sk, pk) = ("./example.sk", "./example.pk");
|
||||
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
|
||||
assert_eq!(Rosenpass::empty(), Rosenpass::default());
|
||||
|
||||
assert_eq!(
|
||||
Rosenpass::from_sk_pk(sk, pk),
|
||||
Rosenpass::new(Some(Keypair::new(pk, sk)))
|
||||
);
|
||||
|
||||
let mut v = Rosenpass::empty();
|
||||
v.keypair = Some(Keypair::new(pk, sk));
|
||||
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
|
||||
}
|
||||
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
36
rosenpass/tests/config_Rosenpass_parse_args_simple.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn parse_simple() {
|
||||
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
|
||||
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||
peer.test:9999 outfile /peer/rp-out";
|
||||
let argv = argv.split(' ').map(|s| s.to_string()).collect();
|
||||
|
||||
let config = Rosenpass::parse_args(argv).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config.keypair,
|
||||
Some(Keypair::new("/my/public-key", "/my/secret-key"))
|
||||
);
|
||||
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||
assert_eq!(
|
||||
&config.listen,
|
||||
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers,
|
||||
vec![RosenpassPeer {
|
||||
public_key: PathBuf::from("/peer/public-key"),
|
||||
endpoint: Some("peer.test:9999".into()),
|
||||
pre_shared_key: None,
|
||||
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||
..Default::default()
|
||||
}]
|
||||
);
|
||||
}
|
||||
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
42
rosenpass/tests/config_Rosenpass_store.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rosenpass::config::{Rosenpass, Verbosity};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_store() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = tmpdir.path().join("config.toml");
|
||||
|
||||
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Can not commit config, path not known
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can store it to an explicit path though
|
||||
c.store(&cfg)?;
|
||||
|
||||
// Storing does not set commitment path
|
||||
assert!(c.commit().is_err());
|
||||
|
||||
// We can reload the config now and the configurations
|
||||
// are equal if we adjust the commitment path
|
||||
let mut c2 = Rosenpass::load(&cfg)?;
|
||||
c.config_file_path = PathBuf::from(&cfg);
|
||||
assert_eq!(c, c2);
|
||||
|
||||
// And this loaded config can now be committed
|
||||
c2.verbosity = Verbosity::Verbose;
|
||||
c2.commit()?;
|
||||
|
||||
// And the changes actually made it to disk
|
||||
let c3 = Rosenpass::load(cfg)?;
|
||||
assert_eq!(c2, c3);
|
||||
assert_ne!(c, c3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
37
rosenpass/tests/config_Rosenpass_validate.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fs;
|
||||
|
||||
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
|
||||
|
||||
#[test]
|
||||
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
|
||||
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
// Empty validates OK
|
||||
assert!(Rosenpass::empty().validate().is_ok());
|
||||
|
||||
// Missing secret key does not pass usefulness
|
||||
assert!(Rosenpass::empty().check_usefullness().is_err());
|
||||
|
||||
let sk = tmpdir.path().join("example.sk");
|
||||
let pk = tmpdir.path().join("example.pk");
|
||||
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
|
||||
|
||||
// Missing secret key does not validate
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But passes usefulness (the configuration is useful but invalid)
|
||||
assert!(cfg.check_usefullness().is_ok());
|
||||
|
||||
// Providing empty key files does not help
|
||||
fs::write(&sk, b"")?;
|
||||
fs::write(&pk, b"")?;
|
||||
assert!(cfg.validate().is_err());
|
||||
|
||||
// But after providing proper key files, the configuration validates
|
||||
generate_and_save_keypair(sk, pk)?;
|
||||
assert!(cfg.validate().is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
15
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
15
rosenpass/tests/gen-ipc-msg-types.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::{borrow::Borrow, process::Command};
|
||||
|
||||
#[test]
|
||||
fn test_gen_ipc_msg_types() -> anyhow::Result<()> {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rosenpass-gen-ipc-msg-types")).output()?;
|
||||
assert!(out.status.success());
|
||||
|
||||
let stdout = String::from_utf8(out.stdout)?;
|
||||
|
||||
// Smoke tests only
|
||||
assert!(stdout.contains("type RawMsgType = u128;"));
|
||||
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9\"));"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -160,6 +160,9 @@ fn check_example_config() {
|
||||
.output()
|
||||
.expect("EXAMPLE_CONFIG not valid");
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(stderr, "");
|
||||
|
||||
fs::copy(
|
||||
tmp_dir.path().join("rp-public-key"),
|
||||
tmp_dir.path().join("rp-peer-public-key"),
|
||||
|
||||
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(())
|
||||
|
||||
50
rp/tests/smoketest.rs
Normal file
50
rp/tests/smoketest.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn smoketest() -> anyhow::Result<()> {
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let secret = tmpdir.path().join("server.secret");
|
||||
let public = tmpdir.path().join("server.public");
|
||||
let invalid = tmpdir.path().join("invalid.secret");
|
||||
let toml = tmpdir.path().join("config.toml");
|
||||
|
||||
let invalid_config = r#"
|
||||
verbose = false
|
||||
private_keys_dir = "invliad"
|
||||
|
||||
[[peers]]
|
||||
public_keys_dir = "invliad"
|
||||
"#;
|
||||
|
||||
// Generate keys
|
||||
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["genkey", secret.to_str().unwrap()])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
assert!(status.success());
|
||||
|
||||
// Derive Public keys
|
||||
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["pubkey", secret.to_str().unwrap(), public.to_str().unwrap()])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
assert!(status.success());
|
||||
|
||||
// Can not exchange keys using exchange with invalid keys
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args(["exchange", invalid.to_str().unwrap()])
|
||||
.output()?;
|
||||
assert!(!out.status.success());
|
||||
|
||||
std::fs::write(toml, invalid_config)?;
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
|
||||
.args([
|
||||
"exchange-config",
|
||||
tmpdir.path().join("invalid_config").to_str().unwrap(),
|
||||
])
|
||||
.output()?;
|
||||
assert!(!out.status.success());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MallocVec] and [MallocBox] types.
|
||||
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
@@ -6,31 +12,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MallocAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MallocAllocator {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
|
||||
|
||||
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
|
||||
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let malloc_box: MallocBox<u8> = malloc_box(data);
|
||||
/// # assert_eq!(*malloc_box, 42u8);
|
||||
/// ```
|
||||
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
|
||||
MallocBox::<T>::new_in(x, MallocAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
|
||||
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
|
||||
/// malloc_vec.push(0u8);
|
||||
/// malloc_vec.push(1u8);
|
||||
/// malloc_vec.push(2u8);
|
||||
/// # let mut element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = malloc_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn malloc_vec<T>() -> MallocVec<T> {
|
||||
MallocVec::<T>::new_in(MallocAllocator::new())
|
||||
}
|
||||
|
||||
impl MallocAllocator {
|
||||
/// Creates a new [MallocAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MallocAllocatorContents,
|
||||
@@ -94,6 +147,9 @@ mod test {
|
||||
malloc_allocation_impl::<8>(&alloc);
|
||||
malloc_allocation_impl::<64>(&alloc);
|
||||
malloc_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
|
||||
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
|
||||
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
|
||||
//!
|
||||
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
|
||||
|
||||
#![cfg(target_os = "linux")]
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
@@ -7,31 +13,78 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemfdSecAllocatorContents;
|
||||
|
||||
/// Memory allocation using using the memsec crate
|
||||
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
|
||||
/// the [allocator_api2] crate.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct MemfdSecAllocator {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
}
|
||||
|
||||
/// A box backed by the memsec allocator
|
||||
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
|
||||
/// from the [memsec] crate.
|
||||
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
|
||||
|
||||
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
|
||||
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
|
||||
/// let data: u8 = 42;
|
||||
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
|
||||
/// # assert_eq!(*memfdsec_box, 42u8);
|
||||
/// ```
|
||||
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
|
||||
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
|
||||
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
|
||||
/// memfdsec_vec.push(0u8);
|
||||
/// memfdsec_vec.push(1u8);
|
||||
/// memfdsec_vec.push(2u8);
|
||||
/// # let mut element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = memfdsec_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
|
||||
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
|
||||
}
|
||||
|
||||
impl MemfdSecAllocator {
|
||||
/// Create a new [MemfdSecAllocator].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_dummy_private_data: MemfdSecAllocatorContents,
|
||||
@@ -95,6 +148,9 @@ mod test {
|
||||
memfdsec_allocation_impl::<8>(&alloc);
|
||||
memfdsec_allocation_impl::<64>(&alloc);
|
||||
memfdsec_allocation_impl::<999>(&alloc);
|
||||
|
||||
// Also test the debug-print for good measure
|
||||
let _ = format!("{:?}", alloc);
|
||||
}
|
||||
|
||||
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
//! This module provides wrappers around the memfdsec and the memsec allocators from the
|
||||
//! [memsec] crate. The wrappers implement the [Allocator](allocator_api2::alloc::Allocator) trait
|
||||
//! and can thus be used as a drop in replacement wherever ever this trait is required.
|
||||
|
||||
pub mod malloc;
|
||||
pub mod memfdsec;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This module provides a [SecretAllocator](SecretAlloc) that allocates memory with extra
|
||||
//! protections that make it more difficult for threat actors to access secrets that they aren't
|
||||
//! authorized to access. At the moment the `memsec` and `memfdsec` allocators from the
|
||||
//! [memsec] crate are supported for this purpose.
|
||||
//!
|
||||
//! [SecretAlloc] implements the [Allocator] trait and can thus be used as a drop in replacement
|
||||
//! wherever ever this trait is required.
|
||||
//!
|
||||
//! The module also provides the [SecretVec] and [SecretBox] types.
|
||||
pub mod memsec;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
@@ -8,15 +17,50 @@ use memsec::malloc::MallocAllocator;
|
||||
#[cfg(target_os = "linux")]
|
||||
use memsec::memfdsec::MemfdSecAllocator;
|
||||
|
||||
/// Globally configures which [SecretAllocType] to use as default for
|
||||
/// [SecretAllocators](SecretAlloc).
|
||||
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
|
||||
|
||||
/// Sets the secret allocation type to use.
|
||||
/// Intended usage at startup before secret allocation
|
||||
/// takes place
|
||||
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
|
||||
/// It is intended that this function is called at startup before a secret allocation
|
||||
/// takes place.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use std::alloc::Layout;
|
||||
/// # use allocator_api2::alloc::Allocator;
|
||||
/// # use rosenpass_secret_memory::alloc::{set_secret_alloc_type, SecretAlloc, SecretAllocType};
|
||||
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// let secret_alloc = SecretAlloc::default();
|
||||
/// unsafe {
|
||||
/// let memory = secret_alloc.allocate(Layout::from_size_align_unchecked(128, 32))?;
|
||||
/// }
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
/// ```
|
||||
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
|
||||
ALLOC_TYPE.set(alloc_type).unwrap();
|
||||
}
|
||||
|
||||
/// Initializes type of allocator to be sued with `alloc_type` if it is not initialized yet. Returns
|
||||
/// the current [SecretAllocType] afterward.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use std::alloc::Layout;
|
||||
/// # use allocator_api2::alloc::Allocator;
|
||||
/// # use rosenpass_secret_memory::alloc::{get_or_init_secret_alloc_type, set_secret_alloc_type,
|
||||
/// # SecretAlloc, SecretAllocType};
|
||||
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// #[cfg(target_os = "linux")] {
|
||||
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMemfdSec);
|
||||
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
|
||||
/// }
|
||||
/// #[cfg(not(target_os = "linux"))] {
|
||||
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMalloc);
|
||||
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
|
||||
/// }
|
||||
///```
|
||||
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
|
||||
*ALLOC_TYPE.get_or_init(|| alloc_type)
|
||||
}
|
||||
@@ -28,6 +72,7 @@ pub enum SecretAllocType {
|
||||
MemsecMemfdSec,
|
||||
}
|
||||
|
||||
/// An [Allocator] that uses a [SecretAllocType] for allocation.
|
||||
pub struct SecretAlloc {
|
||||
alloc_type: SecretAllocType,
|
||||
}
|
||||
@@ -68,19 +113,72 @@ unsafe impl Allocator for SecretAlloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [allocator_api2::boxed::Box] that is backed by [SecretAlloc].
|
||||
pub type SecretBox<T> = allocator_api2::boxed::Box<T, SecretAlloc>;
|
||||
|
||||
/// A vector backed by the memsec allocator
|
||||
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
|
||||
pub type SecretVec<T> = allocator_api2::vec::Vec<T, SecretAlloc>;
|
||||
|
||||
/// Try to allocate a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works. It returns an error if the allocation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_box_try, SecretBox};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let data: u8 = 42;
|
||||
/// let secret_box: SecretBox<u8> = secret_box_try(data)?;
|
||||
/// # assert_eq!(*secret_box, 42u8);
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
|
||||
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
|
||||
}
|
||||
|
||||
/// Allocates a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
|
||||
/// still works.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_box, SecretBox};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let data: u8 = 42;
|
||||
/// let secret_box: SecretBox<u8> = secret_box(data);
|
||||
/// # assert_eq!(*secret_box, 42u8);
|
||||
/// ```
|
||||
pub fn secret_box<T>(x: T) -> SecretBox<T> {
|
||||
SecretBox::<T>::new_in(x, SecretAlloc::default())
|
||||
}
|
||||
|
||||
/// Allocate a [SecretVec] for the type `T`. No memory will be actually allocated
|
||||
/// until elements are pushed to the vector.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use rosenpass_secret_memory::alloc::{secret_vec, SecretVec};
|
||||
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
|
||||
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
|
||||
/// set_secret_alloc_type(MemsecMalloc);
|
||||
/// let mut secret_vec: SecretVec<u8> = secret_vec();
|
||||
/// secret_vec.push(0u8);
|
||||
/// secret_vec.push(1u8);
|
||||
/// secret_vec.push(2u8);
|
||||
/// # let mut element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 2u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 1u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_some());
|
||||
/// # assert_eq!(element.unwrap(), 0u8);
|
||||
/// # element = secret_vec.pop();
|
||||
/// # assert!(element.is_none());
|
||||
/// ```
|
||||
pub fn secret_vec<T>() -> SecretVec<T> {
|
||||
SecretVec::<T>::new_in(SecretAlloc::default())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
//! This module provides a helper for creating debug prints for byte slices.
|
||||
//! See [debug_crypto_array] for more details.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::fmt::{Debug, Formatter};
|
||||
/// use rosenpass_secret_memory::debug::debug_crypto_array;
|
||||
///
|
||||
/// struct U8Wrapper {
|
||||
/// pub u_eigt: Vec<u8>
|
||||
/// }
|
||||
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
|
||||
/// debug_crypto_array(self.u_eigt.as_slice(), f)
|
||||
/// }
|
||||
/// }
|
||||
/// // Short byte slices are printed completely.
|
||||
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
|
||||
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
|
||||
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
|
||||
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
|
||||
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
|
||||
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
|
||||
/// ```
|
||||
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("[{}]=")?;
|
||||
if v.len() > 64 {
|
||||
|
||||
@@ -1,8 +1,46 @@
|
||||
//! Objects that implement this Trait provide a way to store their data in way that respects the
|
||||
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
|
||||
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
|
||||
//! equivalent to [rosenpass_util::file::Visibility::Secret].
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
|
||||
/// be implemented as follows for example:
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::io::Write;
|
||||
/// use std::path::Path;
|
||||
/// use rosenpass_secret_memory::file::StoreSecret;
|
||||
///
|
||||
/// use rosenpass_util::file::{fopen_w, Visibility};
|
||||
///
|
||||
/// struct MyWeirdI32 {
|
||||
/// _priv_i32: [u8; 4],
|
||||
/// }
|
||||
///
|
||||
/// impl StoreSecret for MyWeirdI32 {
|
||||
/// type Error = std::io::Error;
|
||||
///
|
||||
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
|
||||
/// [rosenpass_util::file::Visibility::Secret].
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
|
||||
/// Stores the object. No requirement on the visibility is given, but it is common to store
|
||||
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
//! This library provides functionality for working with secret data and protecting it in
|
||||
//! memory from illegitimate access.
|
||||
//!
|
||||
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
|
||||
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
|
||||
//! We refer to the documentation of these modules for more details on their appropriate usage.
|
||||
//!
|
||||
//! The [policy] module then provides functionality for specifying which of the allocators from
|
||||
//! the [alloc] module should be used.
|
||||
//!
|
||||
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
|
||||
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
|
||||
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
|
||||
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
|
||||
//! # Exmaple
|
||||
//! ```rust
|
||||
//! use zeroize::Zeroize;
|
||||
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
|
||||
//! secret_policy_try_use_memfd_secrets();
|
||||
//! let mut my_secret: Secret<32> = Secret::random();
|
||||
//! my_secret.zeroize();
|
||||
//! ```
|
||||
//!
|
||||
//! # Futher functionality
|
||||
//! In addition to this core functionality, this library provides some more smaller tools.
|
||||
//!
|
||||
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
|
||||
//! that of [Secret].
|
||||
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
|
||||
//! backed by byte arrays or slices, like for example [Secret].
|
||||
//! 3. The [mod@file] module provides functionality to store [Secrets](crate::Secret)
|
||||
//! and [Public] in files such that the file's [Visibility](rosenpass_util::file::Visibility)
|
||||
//! corresponds to the confidentiality of the data.
|
||||
//! 4. The [rand] module provides a simple way of generating randomness.
|
||||
|
||||
pub mod debug;
|
||||
pub mod file;
|
||||
pub mod rand;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
|
||||
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
|
||||
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
|
||||
//! This module provides functionality for just that.
|
||||
|
||||
/// This function sets the `memfdsec` allocator as the default in case it is supported by
|
||||
/// the target and uses the `memsec` allocator otherwise.
|
||||
///
|
||||
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
|
||||
pub fn secret_policy_try_use_memfd_secrets() {
|
||||
let alloc_type = {
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -22,6 +31,8 @@ pub fn secret_policy_try_use_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
|
||||
/// this is only supported on Linux targets.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn secret_policy_use_only_memfd_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
|
||||
@@ -34,6 +45,7 @@ pub fn secret_policy_use_only_memfd_secrets() {
|
||||
log::info!("Secrets will be allocated using {:?}", alloc_type);
|
||||
}
|
||||
|
||||
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
|
||||
pub fn secret_policy_use_only_malloc_secrets() {
|
||||
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,7 +15,21 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
|
||||
/// Contains information in the form of a byte array that may be known to the
|
||||
/// public
|
||||
/// public.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
// TODO: We should get rid of the Public type; just use a normal value
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
@@ -24,75 +38,84 @@ pub struct Public<const N: usize> {
|
||||
}
|
||||
|
||||
impl<const N: usize> Public<N> {
|
||||
/// Create a new [Public] from a byte slice
|
||||
/// Create a new [Public] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
copy_slice(value).to_this(Self::zero)
|
||||
}
|
||||
|
||||
/// Create a new [Public] from a byte array
|
||||
/// Create a new [Public] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Create a zero initialized [Public]
|
||||
/// Create a zero initialized [Public].
|
||||
pub fn zero() -> Self {
|
||||
Self { value: [0u8; N] }
|
||||
}
|
||||
|
||||
/// Create a random initialized [Public]
|
||||
/// Create a random initialized [Public].
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [Public]
|
||||
/// Randomize all bytes in an existing [Public].
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.value.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&self.value, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.value
|
||||
}
|
||||
@@ -101,6 +124,7 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
impl<const N: usize> LoadValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||
@@ -111,6 +135,7 @@ impl<const N: usize> LoadValue for Public<N> {
|
||||
impl<const N: usize> StoreValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, **self)?;
|
||||
Ok(())
|
||||
@@ -118,8 +143,10 @@ impl<const N: usize> StoreValue for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -142,6 +169,7 @@ impl<const N: usize> LoadValueB64 for Public<N> {
|
||||
impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let p = path.as_ref();
|
||||
let mut f = [0u8; F];
|
||||
@@ -155,8 +183,10 @@ impl<const N: usize> StoreValueB64 for Public<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
mut writer: W,
|
||||
@@ -172,89 +202,117 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{Public, PublicBox};
|
||||
///
|
||||
/// let mut my_public_data: Public<32> = Public::random();
|
||||
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
|
||||
///
|
||||
/// // Now we can practically handle it just as we would handle the Public itself:
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_public_data.randomize();
|
||||
/// // A Public can be overwritten with zeros.
|
||||
/// my_public_data.zeroize();
|
||||
/// // If a Public is printed as Debug, its content is printed byte for byte.
|
||||
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
|
||||
/// ```
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct PublicBox<const N: usize> {
|
||||
/// The inner [Box] around the [Public].
|
||||
pub inner: Box<Public<N>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PublicBox<N> {
|
||||
/// Create a new [PublicBox] from a byte slice
|
||||
/// Create a new [PublicBox] from a byte slice.
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::from_slice(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [PublicBox] from a byte array
|
||||
/// Create a new [PublicBox] from a byte array.
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a zero initialized [PublicBox]
|
||||
/// Create a zero initialized [PublicBox].
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a random initialized [PublicBox]
|
||||
/// Create a random initialized [PublicBox].
|
||||
pub fn random() -> Self {
|
||||
Self {
|
||||
inner: Box::new(Public::random()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [PublicBox]
|
||||
/// Randomize all bytes in an existing [PublicBox].
|
||||
pub fn randomize(&mut self) {
|
||||
self.inner.randomize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.inner.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&**self, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Target = [u8; N];
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
self.inner.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow(&self) -> &[u8] {
|
||||
self.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
self.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut p = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
|
||||
@@ -263,22 +321,26 @@ impl<const N: usize> LoadValue for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValue for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory
|
||||
// This is implemented separately from Public to avoid allocating too much stack memory.
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// A vector is used here to ensure heap allocation without copy from stack
|
||||
// A vector is used here to ensure heap allocation without copy from stack.
|
||||
let mut f = vec![0u8; F];
|
||||
let mut v = PublicBox::zero();
|
||||
let p = path.as_ref();
|
||||
@@ -295,16 +357,20 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64 for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
self.inner.store_b64::<F, P>(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||
&self,
|
||||
writer: W,
|
||||
@@ -320,6 +386,7 @@ mod tests {
|
||||
#[allow(clippy::module_inception)]
|
||||
mod tests {
|
||||
use crate::{Public, PublicBox};
|
||||
use rand::Fill;
|
||||
use rosenpass_util::{
|
||||
b64::b64_encode,
|
||||
file::{
|
||||
@@ -449,5 +516,112 @@ mod tests {
|
||||
fn test_public_box_load_store_base64() {
|
||||
run_base64_load_store_test::<PublicBox<N>>();
|
||||
}
|
||||
|
||||
/// Test the debug print function for [Public]
|
||||
#[test]
|
||||
fn test_debug_public() {
|
||||
let p: Public<32> = Public::zero();
|
||||
let _ = format!("{:?}", p);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_sized() {
|
||||
let p: Public<32> = Public::zero();
|
||||
let borrowed: &[u8; 32] = &p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_sized_mut() {
|
||||
let mut p: Public<32> = Public::zero();
|
||||
let borrowed: &mut [u8; 32] = &mut p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_unsized() {
|
||||
use std::borrow::Borrow;
|
||||
let p: Public<32> = Public::zero();
|
||||
let borrowed: &[u8] = p.borrow();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_unsized_mut() {
|
||||
use std::borrow::BorrowMut;
|
||||
let mut p: Public<32> = Public::zero();
|
||||
let borrowed: &mut [u8] = p.borrow_mut();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly created from a slice.
|
||||
#[test]
|
||||
fn test_public_box_from_slice() {
|
||||
let my_slice: &[u8; 32] = &[0; 32];
|
||||
let p: PublicBox<32> = PublicBox::from_slice(my_slice);
|
||||
assert_eq!(p.deref(), my_slice);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] can correctly be created with its [PublicBox::new] function.
|
||||
#[test]
|
||||
fn test_public_box_new() {
|
||||
let pb = PublicBox::new([42; 32]);
|
||||
assert_eq!(pb.deref(), &[42; 32]);
|
||||
}
|
||||
|
||||
/// Test the randomize functionality of [PublicBox].
|
||||
#[test]
|
||||
fn test_public_box_randomize() {
|
||||
let mut pb: PublicBox<32> = PublicBox::zero();
|
||||
pb.randomize();
|
||||
pb.try_fill(&mut crate::rand::rng()).unwrap();
|
||||
// Can't really assert anything here until we have can predict the randomness
|
||||
// by derandomizing the RNG for tests.
|
||||
}
|
||||
|
||||
/// Test the [Debug] print of [PublicBox]
|
||||
#[test]
|
||||
fn test_public_box_debug() {
|
||||
let pb: PublicBox<32> = PublicBox::new([42; 32]);
|
||||
let _ = format!("{:?}", pb);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_box_sized() {
|
||||
let p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &[u8; 32] = &p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a mutable u8 array.
|
||||
#[test]
|
||||
fn test_borrow_public_box_sized_mut() {
|
||||
let mut p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &mut [u8; 32] = &mut p;
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [PublicBox] is correctly borrowed to a u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_box_unsized() {
|
||||
use std::borrow::Borrow;
|
||||
let p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &[u8] = p.borrow();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
|
||||
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
|
||||
#[test]
|
||||
fn test_borrow_public_box_unsized_mut() {
|
||||
use std::borrow::BorrowMut;
|
||||
let mut p: PublicBox<32> = PublicBox::zero();
|
||||
let borrowed: &mut [u8] = p.borrow_mut();
|
||||
assert_eq!(borrowed, &[0; 32]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! This module provides functionality for generating random numbers using the [rand] crate.
|
||||
|
||||
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
|
||||
/// Get the default [Rng].
|
||||
pub fn rng() -> Rng {
|
||||
rand::thread_rng()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ thread_local! {
|
||||
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
|
||||
}
|
||||
|
||||
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
|
||||
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
|
||||
where
|
||||
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
|
||||
@@ -47,37 +48,47 @@ where
|
||||
.unwrap_or_else(|| f(None))
|
||||
}
|
||||
|
||||
// Wrapper around SecretBox that applies automatic zeroization
|
||||
/// Wrapper around SecretBox that applies automatic zeroization.
|
||||
#[derive(Debug)]
|
||||
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
|
||||
|
||||
impl<T: Zeroize> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
|
||||
/// [Zeroize].
|
||||
fn new(boxed: T) -> Self {
|
||||
ZeroizingSecretBox(Some(secret_box(boxed)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
|
||||
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] for the type `T`,
|
||||
/// which must implement [Zeroize] but does not have to be [Sized].
|
||||
fn from_secret_box(inner: SecretBox<T>) -> Self {
|
||||
Self(Some(inner))
|
||||
}
|
||||
|
||||
/// Consumes the [ZeroizingSecretBox] and returns the content in a [SecretBox] for the type `T`,
|
||||
/// which must implement [Zeroize] but does not have to be [Sized].
|
||||
fn take(mut self) -> SecretBox<T> {
|
||||
self.0.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that a Secret is always zeroized when it is dropped.
|
||||
impl<T: Zeroize + ?Sized> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
|
||||
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
|
||||
fn zeroize(&mut self) {
|
||||
if let Some(inner) = &mut self.0 {
|
||||
let inner: &mut SecretBox<T> = inner; // type annotation
|
||||
let inner: &mut SecretBox<T> = inner; // Type annotation.
|
||||
inner.zeroize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
||||
/// Releases the memory of this [ZeroizingSecretBox]. In contrast to usual implementations
|
||||
/// of [Drop], we zeroize the memory before dropping it. This fulfills the promises we make
|
||||
/// by implementing [ZeroizeOnDrop].
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
@@ -86,29 +97,32 @@ impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
|
||||
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
|
||||
type Target = T;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref(&self) -> &T {
|
||||
self.0.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.0.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool that stores secret memory allocations
|
||||
/// Pool that stores secret memory allocations.
|
||||
///
|
||||
/// Allocation of secret memory is expensive. Thus, this struct provides a
|
||||
/// pool of secret memory, readily available to yield protected, slices of
|
||||
/// memory.
|
||||
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
||||
struct SecretMemoryPool {
|
||||
/// A pool to reuse secret memory
|
||||
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
|
||||
}
|
||||
|
||||
impl SecretMemoryPool {
|
||||
/// Create a new [SecretMemoryPool]
|
||||
/// Create a new [SecretMemoryPool].
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -116,7 +130,7 @@ impl SecretMemoryPool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return secret back to the pool for future re-use
|
||||
/// Return secret back to the pool for future re-use.
|
||||
pub fn release<const N: usize>(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) {
|
||||
sec.zeroize();
|
||||
|
||||
@@ -134,7 +148,7 @@ impl SecretMemoryPool {
|
||||
/// Take protected memory from the pool, allocating new one if no suitable
|
||||
/// chunk is found in the inventory.
|
||||
///
|
||||
/// The secret is guaranteed to be full of nullbytes
|
||||
/// The secret is guaranteed to be full of nullbytes.
|
||||
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
|
||||
let entry = self.pool.entry(N).or_default();
|
||||
let inner = match entry.pop() {
|
||||
@@ -145,22 +159,50 @@ impl SecretMemoryPool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage for secret data
|
||||
/// [Secret] stores its memory in a way that makes it difficult for threat actors to access it
|
||||
/// without permission. This is achieved through the following mechanisms:
|
||||
/// 1. Data in a [Secret] is stored in memory that is allocated and managed by memory allocators
|
||||
/// that make it more difficult for threat actors to access the memory. Specifically, the
|
||||
/// allocators from [memsec] are supported.
|
||||
/// 2. Memory that is allocated for a [Secret] is zeroized before it is used for anything else.
|
||||
///
|
||||
/// In order to use a [Secret], we have to decide on the secure allocator to use beforehand
|
||||
/// calling either
|
||||
/// [secret_policy_use_only_malloc_secrets](crate::secret_policy_use_only_malloc_secrets),
|
||||
/// [secret_policy_use_only_memfd_secrets](crate::secret_policy_use_only_memfd_secrets)
|
||||
/// or [secret_policy_try_use_memfd_secrets](crate::secret_policy_try_use_memfd_secrets).
|
||||
///
|
||||
/// You can use a [Secret] as follows:
|
||||
/// ```rust
|
||||
/// # use zeroize::Zeroize;
|
||||
/// # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
///
|
||||
/// // We have to define the security policy before using Secrets.
|
||||
/// secret_policy_use_only_malloc_secrets();
|
||||
/// let mut my_secret: Secret<32> = Secret::zero();
|
||||
/// // Fill with some random data that I can use a cryptographic key later on.
|
||||
/// my_secret.randomize();
|
||||
/// // In case I accidentally print my secret in a debug, it's still not leaked:
|
||||
/// assert_eq!(format!("{:?}", my_secret), "<SECRET>");
|
||||
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
|
||||
/// my_secret.zeroize();
|
||||
/// ```
|
||||
pub struct Secret<const N: usize> {
|
||||
storage: Option<ZeroizingSecretBox<[u8; N]>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> Secret<N> {
|
||||
/// Create a new [Secret] from a byte-slice.
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
let mut new_self = Self::zero();
|
||||
new_self.secret_mut().copy_from_slice(slice);
|
||||
new_self
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is zero initialized
|
||||
/// Returns a new [Secret] that is zero initialized.
|
||||
pub fn zero() -> Self {
|
||||
// Using [SecretMemoryPool] here because this operation is expensive,
|
||||
// yet it is used in hot loops
|
||||
// yet it is used in hot loops.
|
||||
let buf = with_secret_memory_pool(|pool| {
|
||||
pool.map(|p| p.take())
|
||||
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
|
||||
@@ -169,28 +211,30 @@ impl<const N: usize> Secret<N> {
|
||||
Self { storage: Some(buf) }
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is randomized
|
||||
/// Returns a new [Secret] that is randomized.
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Sets all data an existing secret to random bytes
|
||||
/// Sets all data of an existing [Secret] to random bytes.
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data
|
||||
/// Borrows the data.
|
||||
pub fn secret(&self) -> &[u8; N] {
|
||||
self.storage.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data mutably
|
||||
/// Borrows the data mutably.
|
||||
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
||||
self.storage.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Secret<N> {
|
||||
/// Tries to fill this [Secret] with random data. The [Secret] is first zeroized
|
||||
/// to make sure that the barriers from the zeroize crate take effect.
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
// Zeroize self first just to make sure the barriers from the zeroize create take
|
||||
// effect to prevent the compiler from optimizing this away.
|
||||
@@ -200,8 +244,10 @@ impl<const N: usize> Randomize for Secret<N> {
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that a [Secret] is always zeroized when it is dropped.
|
||||
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
|
||||
impl<const N: usize> Zeroize for Secret<N> {
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn zeroize(&mut self) {
|
||||
if let Some(inner) = &mut self.storage {
|
||||
inner.zeroize()
|
||||
@@ -209,7 +255,14 @@ impl<const N: usize> Zeroize for Secret<N> {
|
||||
}
|
||||
}
|
||||
|
||||
// Our implementation of Drop gives back the allocated secret memory to the secret memory pool.
|
||||
impl<const N: usize> Drop for Secret<N> {
|
||||
/// Release the memory of this [Secret]. In contrast to usual implementations to [Drop] we
|
||||
/// do the following:
|
||||
/// 1. The memory of this [Secret] gets zeroized as required by [ZeroizeOnDrop].
|
||||
/// 2. The memory is returned to a memory pool of specially secure memory to be reused.
|
||||
///
|
||||
/// This behaviour fulfills the promises we make by implementing [ZeroizeOnDrop].
|
||||
fn drop(&mut self) {
|
||||
with_secret_memory_pool(|pool| {
|
||||
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
|
||||
@@ -240,6 +293,7 @@ impl<const N: usize> fmt::Debug for Secret<N> {
|
||||
impl<const N: usize> LoadValue for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
@@ -253,6 +307,7 @@ impl<const N: usize> LoadValue for Secret<N> {
|
||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut f: Secret<F> = Secret::random();
|
||||
let mut v = Self::random();
|
||||
@@ -272,6 +327,7 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
let p = path.as_ref();
|
||||
|
||||
@@ -291,6 +347,7 @@ impl<const N: usize> StoreValueB64 for Secret<N> {
|
||||
impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
|
||||
let mut f: Secret<F> = Secret::random();
|
||||
let encoded_str = b64_encode(self.secret(), f.secret_mut())
|
||||
@@ -307,11 +364,13 @@ impl<const N: usize> StoreValueB64Writer for Secret<N> {
|
||||
impl<const N: usize> StoreSecret for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// No extra documentation here because the Trait already provides a good documentation.
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
|
||||
Ok(())
|
||||
@@ -320,7 +379,10 @@ impl<const N: usize> StoreSecret for Secret<N> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test_spawn_process_provided_policies;
|
||||
use crate::{
|
||||
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
|
||||
test_spawn_process_provided_policies,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use std::{fs, os::unix::fs::PermissionsExt};
|
||||
@@ -328,7 +390,7 @@ mod test {
|
||||
|
||||
procspawn::enable_test_support!();
|
||||
|
||||
/// check that we can alloc using the magic pool
|
||||
/// Check that we can alloc using the magic pool.
|
||||
#[test]
|
||||
fn secret_memory_pool_take() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -339,7 +401,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// check that a secret lives, even if its [SecretMemoryPool] is deleted
|
||||
/// Check that a secret lives, even if its [SecretMemoryPool] is deleted.
|
||||
#[test]
|
||||
fn secret_memory_pool_drop() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -351,7 +413,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// check that a secret can be reborn, freshly initialized with zero
|
||||
/// Check that a secret can be reborn, freshly initialized with zero.
|
||||
#[test]
|
||||
fn secret_memory_pool_release() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -372,7 +434,7 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// test loading a secret from an example file, and then storing it again in a different file
|
||||
/// Test loading a secret from an example file, and then storing it again in a different file.
|
||||
#[test]
|
||||
fn test_secret_load_store() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -409,7 +471,8 @@ mod test {
|
||||
});
|
||||
}
|
||||
|
||||
/// test loading a base64 encoded secret from an example file, and then storing it again in a different file
|
||||
/// Test loading a base64 encoded secret from an example file, and then storing it again in
|
||||
/// a different file.
|
||||
#[test]
|
||||
fn test_secret_load_store_base64() {
|
||||
test_spawn_process_provided_policies!({
|
||||
@@ -463,4 +526,33 @@ mod test {
|
||||
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test the creation of a [ZeroizingSecretBox] using its [new](ZeroizingSecretBox::new)
|
||||
/// function.
|
||||
#[test]
|
||||
fn test_zeroizing_secret_box_new() {
|
||||
struct DummyZeroizing {
|
||||
inner_dummy: [u8; 32],
|
||||
}
|
||||
impl Zeroize for DummyZeroizing {
|
||||
fn zeroize(&mut self) {
|
||||
self.inner_dummy = [0; 32];
|
||||
}
|
||||
}
|
||||
let dummy = DummyZeroizing {
|
||||
inner_dummy: [42; 32],
|
||||
};
|
||||
secret_policy_use_only_malloc_secrets();
|
||||
let mut zsb: ZeroizingSecretBox<DummyZeroizing> = ZeroizingSecretBox::new(dummy);
|
||||
zsb.zeroize();
|
||||
assert_eq!(zsb.inner_dummy, [0; 32]);
|
||||
}
|
||||
|
||||
/// Test the debug print of [Secret].
|
||||
#[test]
|
||||
fn test_debug_secret() {
|
||||
secret_policy_use_only_malloc_secrets();
|
||||
let my_secret: Secret<32> = Secret::zero();
|
||||
assert_eq!(format!("{:?}", my_secret), "<SECRET>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ use rosenpass_to::{to, with_destination, To};
|
||||
use std::ops::BitXorAssign;
|
||||
|
||||
// Destination functions return some value that implements the To trait.
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would
|
||||
// be without destination parameters
|
||||
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: BitXorAssign + Clone,
|
||||
{
|
||||
// Custom implementations of the to trait can be created, but the easiest
|
||||
// way to create them is to use the provided helper functions like with_destination.
|
||||
with_destination(move |dst: &mut [T]| {
|
||||
assert!(src.len() == dst.len());
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Functions with destination copying data between slices and arrays.
|
||||
//! Functions that make it easy to copy data between arrays and slices using functions with
|
||||
//! destinations. See the specific functions for examples and more explanations.
|
||||
|
||||
use crate::{with_destination, To};
|
||||
|
||||
@@ -8,6 +9,17 @@ use crate::{with_destination, To};
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the two slices have different lengths.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice;
|
||||
/// let to_function = copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_slice<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -23,6 +35,19 @@ where
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if destination is shorter than origin.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least_src;
|
||||
/// let to_function = copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -34,6 +59,18 @@ where
|
||||
/// destination.
|
||||
///
|
||||
/// Copies as much data as is present in the shorter slice.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::copy_slice_least;
|
||||
/// let to_function = copy_slice_least(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn copy_slice_least<T>(origin: &[T]) -> impl To<[T], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -47,6 +84,24 @@ where
|
||||
/// Function with destination that attempts to copy data from origin into the destination.
|
||||
///
|
||||
/// Will return None if the slices are of different lengths.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice;
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the slices do not have the same length.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice(&[0; 16]);
|
||||
/// let mut dst = [255; 16];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn try_copy_slice<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -62,6 +117,26 @@ where
|
||||
/// Destination may be longer than origin.
|
||||
///
|
||||
/// Will return None if the destination is shorter than origin.
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::To;
|
||||
/// # use crate::rosenpass_to::ops::try_copy_slice_least_src;
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 15];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This will return None because the destination is to short.
|
||||
/// assert!(result.is_none());
|
||||
///
|
||||
/// let to_function = try_copy_slice_least_src(&[0; 16]);
|
||||
/// let mut dst = [255; 32];
|
||||
/// let result = to_function.to(&mut dst);
|
||||
/// // This time it works:
|
||||
/// assert!(result.is_some());
|
||||
/// // After the operation, the first half of `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst[0..16].iter().all(|b| *b == 0));
|
||||
/// // The second half will have remained the same.
|
||||
/// assert!(dst[16..32].iter().all(|b| *b == 255));
|
||||
/// ```
|
||||
pub fn try_copy_slice_least_src<T>(origin: &[T]) -> impl To<[T], Option<()>> + '_
|
||||
where
|
||||
T: Copy,
|
||||
@@ -72,6 +147,18 @@ where
|
||||
}
|
||||
|
||||
/// Function with destination that copies all data between two array references.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_to::ops::copy_array;
|
||||
/// use rosenpass_to::To;
|
||||
/// let my_arr: [u8; 32] = [0; 32];
|
||||
/// let to_function = copy_array(&my_arr);
|
||||
/// let mut dst = [255; 32];
|
||||
/// to_function.to(&mut dst);
|
||||
/// // After the operation `dst` will hold the same data as the original slice.
|
||||
/// assert!(dst.iter().all(|b| *b == 0));
|
||||
/// ```
|
||||
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
|
||||
where
|
||||
T: Copy,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
//! This module provides the [Beside] struct. In the context of functions with targets,
|
||||
//! [Beside] structures the destination value and the return value unmistakably and offers useful
|
||||
//! helper functions to work with them.
|
||||
|
||||
use crate::CondenseBeside;
|
||||
|
||||
/// Named tuple holding the return value and the output from a function with destinations.
|
||||
/// Named tuple holding the return value and the destination from a function with destinations.
|
||||
/// See the respective functions for usage examples.
|
||||
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||
|
||||
@@ -59,7 +64,7 @@ impl<Val, Ret> Beside<Val, Ret> {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
/// Perform beside condensation. See [CondenseBeside]
|
||||
/// Perform beside condensation. See [CondenseBeside] for more details.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
@@ -90,3 +95,25 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
|
||||
(val, ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Beside;
|
||||
|
||||
#[test]
|
||||
fn from_tuple() {
|
||||
let tuple = (21u8, 42u16);
|
||||
let beside: Beside<u8, u16> = Beside::from(tuple);
|
||||
assert_eq!(beside.dest(), &21u8);
|
||||
assert_eq!(beside.ret(), &42u16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_beside() {
|
||||
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
|
||||
type U8u16 = (u8, u16);
|
||||
let tuple = U8u16::from(beside);
|
||||
assert_eq!(tuple.0, 21u8);
|
||||
assert_eq!(tuple.1, 42u16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/// Beside condensation.
|
||||
//! This module provides condensation for values that stand side by side,
|
||||
//! which is often useful when working with destination parameters. See [CondenseBeside]
|
||||
//! for more details.
|
||||
|
||||
/// Condenses two values that stand beside each other into one value.
|
||||
/// For example, a blanked implementation for [Result<(), Error>](Result) is provided. If
|
||||
/// `condense(val)` is called on such an object, a [Result<Val, Error>](Result) will
|
||||
/// be returned, if `val` is of type `Val`.
|
||||
///
|
||||
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
|
||||
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
|
||||
@@ -6,6 +13,19 @@
|
||||
///
|
||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||
/// condense trait.
|
||||
///
|
||||
/// # Example
|
||||
/// As an example implementation, we take a look at the blanket implementation for [Option]
|
||||
/// ```ignore
|
||||
/// impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
/// type Condensed = Option<Val>;
|
||||
///
|
||||
/// /// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
/// fn condense(self, ret: Val) -> Option<Val> {
|
||||
/// self.map(|()| ret)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait CondenseBeside<Val> {
|
||||
/// The type that results from condensation.
|
||||
type Condensed;
|
||||
@@ -17,6 +37,7 @@ pub trait CondenseBeside<Val> {
|
||||
impl<Val> CondenseBeside<Val> for () {
|
||||
type Condensed = Val;
|
||||
|
||||
/// Replaces this empty tuple with `ret`.
|
||||
fn condense(self, ret: Val) -> Val {
|
||||
ret
|
||||
}
|
||||
@@ -25,6 +46,7 @@ impl<Val> CondenseBeside<Val> for () {
|
||||
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
type Condensed = Result<Val, Error>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Result] with `ret`.
|
||||
fn condense(self, ret: Val) -> Result<Val, Error> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
@@ -33,6 +55,7 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
type Condensed = Option<Val>;
|
||||
|
||||
/// Replaces the empty tuple inside this [Option] with `ret`.
|
||||
fn condense(self, ret: Val) -> Option<Val> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
/// Helper performing explicit unsized coercion.
|
||||
/// Used by the [to](crate::to()) function.
|
||||
//! This module provides explicit type coercion from [Sized] types to [?Sized][core::marker::Sized]
|
||||
//! types. See [DstCoercion] for more details.
|
||||
|
||||
/// Helper Trait for performing explicit coercion from [Sized] types to
|
||||
/// [?Sized][core::marker::Sized] types. It's used by the [to](crate::to()) function.
|
||||
///
|
||||
/// We provide blanket implementations for any [Sized] type and for any array of [Sized] types.
|
||||
///
|
||||
/// # Example
|
||||
/// It can be used as follows:
|
||||
/// ```
|
||||
/// # use rosenpass_to::DstCoercion;
|
||||
/// // Consider a sized type like this example:
|
||||
/// struct SizedStruct {
|
||||
/// x: u32
|
||||
/// }
|
||||
/// // Then we can coerce it to be unsized:
|
||||
/// let mut sized = SizedStruct { x: 42 };
|
||||
/// assert_eq!(42, sized.coerce_dest().x);
|
||||
///
|
||||
/// // Analogously, we can coerce arrays to slices:
|
||||
/// let mut sized_array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
/// let un_sized: &[i32] = sized_array.coerce_dest();
|
||||
/// assert_eq!(un_sized, un_sized);
|
||||
/// ```
|
||||
pub trait DstCoercion<Dst: ?Sized> {
|
||||
/// Performs an explicit coercion to the destination type.
|
||||
fn coerce_dest(&mut self) -> &mut Dst;
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
||||
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
||||
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or
|
||||
//! some sized variant of
|
||||
//! `Dst` (e.g. `[u8; 64]`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The
|
||||
//! ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as
|
||||
//! `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>)
|
||||
//! – The combiation of Val and Ret after condensing was applied
|
||||
//! (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
|
||||
pub mod beside;
|
||||
pub mod condense;
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
//! This module provides the [To::to] function which allows to use functions with destination in
|
||||
//! a manner akin to that of a variable assignment. See [To::to] for more details.
|
||||
|
||||
use crate::{DstCoercion, To};
|
||||
|
||||
/// Alias for [To::to] moving the destination to the left.
|
||||
///
|
||||
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
|
||||
/// the variable to assign to on the left and the generating function on the right.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// // Using the to function to have data flowing from the right to the left,
|
||||
/// // performing something akin to a variable assignment.
|
||||
/// use rosenpass_to::ops::copy_slice_least;
|
||||
/// # use rosenpass_to::to;
|
||||
/// let mut dst = b" ".to_vec();
|
||||
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
|
||||
/// assert_eq!(&dst[..], b"Hello World");
|
||||
/// ```
|
||||
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
|
||||
where
|
||||
Coercable: ?Sized + DstCoercion<Dst>,
|
||||
|
||||
@@ -1,12 +1,46 @@
|
||||
//! Module that contains the [To] crate which is the container used to
|
||||
//! implement the core functionality of this crate.
|
||||
|
||||
use crate::{Beside, CondenseBeside};
|
||||
use std::borrow::BorrowMut;
|
||||
|
||||
/// The To trait is the core of the to crate; most functions with destinations will either return
|
||||
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
||||
/// Return_value`.
|
||||
/// an object that is an instance of this trait, or they will return `-> impl To<Destination,
|
||||
/// Return_value>`.
|
||||
///
|
||||
/// A quick way to implement a function with destination is to use the
|
||||
/// [with_destination(|param: &mut Type| ...)] higher order function.
|
||||
/// [with_destination(|param: &mut Type| ...)](crate::with_destination) higher order function.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a very simple example for how the Trait can be implemented. More examples for
|
||||
/// how this Trait is best implemented can be found in the overall [crate documentation](crate).
|
||||
/// ```
|
||||
/// use rosenpass_to::To;
|
||||
///
|
||||
/// // This is a simple wrapper around a String that can be written into a byte array using to.
|
||||
/// struct StringToBytes {
|
||||
/// inner: String
|
||||
/// }
|
||||
///
|
||||
/// impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// let bytes = self.inner.as_bytes();
|
||||
/// if bytes.len() > out.len() {
|
||||
/// return Err("out is to short".to_string());
|
||||
/// }
|
||||
/// for i in 0..bytes.len() {
|
||||
/// (*out)[i] = bytes[i];
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let mut buffer: [u8; 10] = [0; 10];
|
||||
/// let result = string_to_bytes.to(&mut buffer);
|
||||
/// assert_eq!(buffer, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
||||
///
|
||||
@@ -19,6 +53,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [self.to_this_beside]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let Beside(dst, result) = string_to_bytes.to_this_beside(|| [0; 10]);
|
||||
/// assert_eq!(dst, [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: BorrowMut<Dst>,
|
||||
@@ -31,10 +95,21 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
|
||||
/// Generate a destination on the fly using default.
|
||||
///
|
||||
/// Uses [Default] to create a value,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// Uses [Default] to create a value, calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value_beside](To::to_value_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let Beside(dst, ret) = copy_array(&[42u8; 16]).to_value_beside();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value_beside(self) -> Beside<Dst, Ret>
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -53,6 +128,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// when the Destination is unsized.
|
||||
///
|
||||
/// This could be the case when the destination is an `[u8]` for instance.
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect_beside](To::collect_beside).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let Beside(dst, ret) = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect_beside<Val>(self) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
@@ -64,6 +152,36 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
|
||||
/// # Example
|
||||
/// Below, we rewrite the example for the overall [To]-Trait and simplify it by using
|
||||
/// [Self::to_this]. We refer to the overall [crate documentation](crate)
|
||||
/// for more examples and general explanations.
|
||||
/// ```
|
||||
/// # use rosenpass_to::To;
|
||||
/// use rosenpass_to::Beside;
|
||||
/// # struct StringToBytes {
|
||||
/// # inner: String
|
||||
/// # }
|
||||
///
|
||||
/// # impl To<[u8], Result<(), String>> for StringToBytes {
|
||||
/// # fn to(self, out: &mut [u8]) -> Result<(), String> {
|
||||
/// # let bytes = self.inner.as_bytes();
|
||||
/// # if bytes.len() > out.len() {
|
||||
/// # return Err("out is to short".to_string());
|
||||
/// # }
|
||||
/// # for i in 0..bytes.len() {
|
||||
/// # (*out)[i] = bytes[i];
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// // StringToBytes is taken from the overall Trait example.
|
||||
/// let string_to_bytes = StringToBytes { inner: "my message".to_string() };
|
||||
/// let result = string_to_bytes.to_this_beside(|| [0; 10]).condense();
|
||||
/// assert!(result.is_ok());
|
||||
/// assert_eq!(result.unwrap(), [109, 121, 32, 109, 101, 115, 115, 97, 103, 101]);
|
||||
///
|
||||
/// ```
|
||||
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Ret: CondenseBeside<Val>,
|
||||
@@ -77,6 +195,18 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [to_value](To::to_value).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
/// let dst = copy_array(&[42u8; 16]).to_value_beside().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
@@ -89,6 +219,19 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
|
||||
///
|
||||
/// # Example
|
||||
/// Below, we provide a simple example for the usage of [collect](To::collect).
|
||||
/// We refer to the overall [crate documentation](crate) for more examples and general
|
||||
/// explanations.
|
||||
/// ```
|
||||
/// use rosenpass_to::Beside;
|
||||
/// use rosenpass_to::To;
|
||||
/// use rosenpass_to::ops::*;
|
||||
///
|
||||
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
|
||||
/// assert_eq!(dst, [42u8; 16]);
|
||||
/// ```
|
||||
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
//! The module provides the [with_destination] function, which makes it easy to create
|
||||
//! a [To] from a lambda function. See [with_destination] and the [crate documentation](crate)
|
||||
//! for more details and examples.
|
||||
|
||||
use crate::To;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A struct that wraps a closure and implements the `To` trait
|
||||
/// A struct that wraps a closure and implements the `To` trait.
|
||||
///
|
||||
/// This allows passing closures that operate on a destination type `Dst`
|
||||
/// and return `Ret`.
|
||||
/// and return `Ret`. It is only internally used to implement [with_destination].
|
||||
///
|
||||
/// # Type Parameters
|
||||
/// * `Dst` - The destination type the closure operates on
|
||||
/// * `Ret` - The return type of the closure
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||
/// * `Dst` - The destination type the closure operates on.
|
||||
/// * `Ret` - The return type of the closure.
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`.
|
||||
struct ToClosure<Dst, Ret, Fun>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
@@ -17,11 +21,11 @@ where
|
||||
{
|
||||
/// The function to call.
|
||||
fun: Fun,
|
||||
/// Phantom data to hold the destination type
|
||||
/// Phantom data to hold the destination type.
|
||||
_val: PhantomData<Box<Dst>>,
|
||||
}
|
||||
|
||||
/// Implementation of the `To` trait for ToClosure
|
||||
/// Implementation of the `To` trait for ToClosure.
|
||||
///
|
||||
/// This enables calling the wrapped closure with a destination reference.
|
||||
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||
@@ -33,6 +37,7 @@ where
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `out` - Mutable reference to the destination
|
||||
/// See the tutorial in [readme.md] for examples and more explanations.
|
||||
fn to(self, out: &mut Dst) -> Ret {
|
||||
(self.fun)(out)
|
||||
}
|
||||
@@ -48,7 +53,24 @@ where
|
||||
/// * `Ret` - The return type of the closure
|
||||
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
|
||||
///
|
||||
/// See the tutorial in [readme.me]..
|
||||
/// See the tutorial in the [crate documentation](crate) for more examples and more explanations.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_to::with_destination;
|
||||
/// use crate::rosenpass_to::To;
|
||||
/// let my_origin_data: [u8; 16]= [2; 16];
|
||||
/// let times_two = with_destination( move |dst: &mut [u8; 16]| {
|
||||
/// for (dst, org) in dst.iter_mut().zip(my_origin_data.iter()) {
|
||||
/// *dst = dst.clone() * org;
|
||||
/// }
|
||||
/// });
|
||||
/// let mut dst: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||||
/// times_two.to(&mut dst);
|
||||
/// for i in 0..16 {
|
||||
/// assert_eq!(dst[i], (2 * i) as u8);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
|
||||
@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
|
||||
|
||||
/// A type that can be incrementally built from a type that can [Build] it
|
||||
///
|
||||
/// This is similar to an option, where [Self::Void] is [std::Option::None],
|
||||
/// [Self::Product] is [std::Option::Some], except that there is a third
|
||||
/// This is similar to an option, where [Self::Void] is [std::option::Option::None],
|
||||
/// [Self::Product] is [std::option::Option::Some], except that there is a third
|
||||
/// intermediate state [Self::Builder] that represents a Some/Product value
|
||||
/// in the process of being made.
|
||||
///
|
||||
@@ -508,9 +508,9 @@ where
|
||||
matches!(self, Self::Void)
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`InProgress`].
|
||||
/// Returns `true` if the construction site is in the [`Builder`] phase.
|
||||
///
|
||||
/// [`InProgress`]: ConstructionSite::InProgress
|
||||
/// [`Builder`]: ConstructionSite::Builder
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -541,9 +541,10 @@ where
|
||||
matches!(self, Self::Builder(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`Done`].
|
||||
/// Returns `true` if the construction site is in the [`Product`] phase and
|
||||
/// is therefore done.
|
||||
///
|
||||
/// [`Done`]: ConstructionSite::Done
|
||||
/// [`Product`]: ConstructionSite::Product
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// A collection of control flow utility macros
|
||||
//! A collection of control flow utility macros
|
||||
|
||||
#[macro_export]
|
||||
/// A simple for loop to repeat a $body a number of times
|
||||
@@ -33,7 +33,7 @@ macro_rules! repeat {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 0);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_unless!(false, 1);
|
||||
/// 0
|
||||
@@ -65,7 +65,7 @@ macro_rules! return_unless {
|
||||
/// 0
|
||||
/// }
|
||||
/// assert_eq!(test_fn(), 1);
|
||||
|
||||
///
|
||||
/// fn test_fn2() -> i32 {
|
||||
/// return_if!(false, 1);
|
||||
/// 0
|
||||
@@ -98,7 +98,7 @@ macro_rules! return_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 5);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for _ in 0..10 {
|
||||
/// for j in 0..20 {
|
||||
@@ -134,7 +134,7 @@ macro_rules! break_if {
|
||||
/// sum += 1;
|
||||
/// }
|
||||
/// assert_eq!(sum, 9);
|
||||
|
||||
///
|
||||
/// let mut sum = 0;
|
||||
/// 'one: for i in 0..10 {
|
||||
/// continue_if!(i == 5, 'one);
|
||||
|
||||
141
util/src/fd.rs
141
util/src/fd.rs
@@ -89,6 +89,30 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
||||
///
|
||||
/// Will panic if the given file descriptor is negative of or larger than
|
||||
/// the file descriptor numbers permitted by the operating system.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::io::Read;
|
||||
/// # use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
/// # use std::os::fd::IntoRawFd;
|
||||
/// # use rustix::fd::AsFd;
|
||||
/// # use rosenpass_util::fd::mask_fd;
|
||||
///
|
||||
/// // Open a temporary file
|
||||
/// let fd = tempfile::tempfile().unwrap().into_raw_fd();
|
||||
/// assert!(fd >= 0);
|
||||
///
|
||||
/// // Mask the file descriptor
|
||||
/// mask_fd(fd).unwrap();
|
||||
///
|
||||
/// // Verify the file descriptor now points to `/dev/null`
|
||||
/// // Reading from `/dev/null` always returns 0 bytes
|
||||
/// let mut replaced_file = unsafe { File::from_raw_fd(fd) };
|
||||
/// let mut buffer = [0u8; 4];
|
||||
/// let bytes_read = replaced_file.read(&mut buffer).unwrap();
|
||||
/// assert_eq!(bytes_read, 0);
|
||||
/// ```
|
||||
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
||||
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting,
|
||||
// it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd
|
||||
@@ -286,14 +310,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinguish different socket address familys; e.g. IP and unix sockets
|
||||
/// Distinguish different socket address families; e.g. IP and unix sockets
|
||||
#[cfg(target_os = "linux")]
|
||||
pub trait GetSocketDomain {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Retrieve the socket domain (address family)
|
||||
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
|
||||
/// Alias for [socket_domain]
|
||||
/// Alias for [Self::socket_domain]
|
||||
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
|
||||
self.socket_domain()
|
||||
}
|
||||
@@ -320,9 +344,67 @@ where
|
||||
pub trait GetUnixSocketType {
|
||||
/// Error type returned by operations in this trait
|
||||
type Error;
|
||||
/// Check if the socket is a unix stream socket
|
||||
|
||||
/// Checks whether the socket is a Unix stream socket.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(true)` if the socket is a Unix stream socket.
|
||||
/// - `Ok(false)` if the socket is not a Unix stream socket.
|
||||
/// - `Err(Self::Error)` if there is an error while performing the check.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::UnixListener;
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().is_unix_stream_socket(), Ok(true)));
|
||||
/// ```
|
||||
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
|
||||
/// Returns Ok(()) only if the underlying socket is a unix stream socket
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::fs::File;
|
||||
/// # use std::os::fd::{AsFd, BorrowedFd};
|
||||
/// # use std::os::unix::net::{UnixDatagram, UnixListener};
|
||||
/// # use tempfile::NamedTempFile;
|
||||
/// # use rosenpass_util::fd::GetUnixSocketType;
|
||||
/// let f = {
|
||||
/// // Generate a temp file and take its path
|
||||
/// // Remove the temp file
|
||||
/// // Create a unix socket on the temp path that is not unused
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixListener::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert!(matches!(f.as_fd().demand_unix_stream_socket(), Ok(())));
|
||||
/// // Error if the FD is a file
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// assert_eq!(temp_file.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Socket operation on non-socket (os error 88)"
|
||||
/// );
|
||||
/// // Error if the FD is a Unix stream with a wrong mode (e.g. Datagram)
|
||||
/// let f = {
|
||||
/// let temp_file = NamedTempFile::new().unwrap();
|
||||
/// let socket_path = temp_file.path().to_owned();
|
||||
/// std::fs::remove_file(&socket_path).unwrap();
|
||||
/// UnixDatagram::bind(socket_path).unwrap()
|
||||
/// };
|
||||
/// assert_eq!(f.as_fd().demand_unix_stream_socket().err().unwrap().to_string(),
|
||||
/// "Expected unix socket in stream mode, but mode is SocketType(2)"
|
||||
/// );
|
||||
/// ```
|
||||
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
@@ -352,16 +434,65 @@ where
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Distinguish between different network socket protocols (e.g. tcp, udp)
|
||||
pub trait GetSocketProtocol {
|
||||
/// Retrieve the socket protocol
|
||||
/// Retrieves the socket's protocol.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Some(Protocol))`: The protocol of the socket if available.
|
||||
/// - `Ok(None)`: If the protocol information is unavailable.
|
||||
/// - `Err(rustix::io::Errno)`: If an error occurs while retrieving the protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().socket_protocol().unwrap().unwrap(), rustix::net::ipproto::UDP);
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
|
||||
/// Check if the socket is a udp socket
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(socket.as_fd().is_udp_socket().unwrap());
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert!(!socket.as_fd().is_udp_socket().unwrap());
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
|
||||
self.socket_protocol()?
|
||||
.map(|p| p == rustix::net::ipproto::UDP)
|
||||
.unwrap_or(false)
|
||||
.ok()
|
||||
}
|
||||
/// Return Ok(()) only if the socket is a udp socket
|
||||
|
||||
/// Ensures that the socket is a UDP socket, returning an error otherwise.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(())` if the socket is a UDP socket.
|
||||
/// - `Err(anyhow::Error)` if the socket is not a UDP socket or if an error occurs retrieving the socket protocol.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::net::UdpSocket;
|
||||
/// # use std::net::TcpListener;
|
||||
/// # use std::os::fd::{AsFd, AsRawFd};
|
||||
/// # use rosenpass_util::fd::GetSocketProtocol;
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:0")?;
|
||||
/// assert!(matches!(socket.as_fd().demand_udp_socket(), Ok(())));
|
||||
///
|
||||
/// let socket = TcpListener::bind("127.0.0.1:0")?;
|
||||
/// assert_eq!(socket.as_fd().demand_udp_socket().unwrap_err().to_string(),
|
||||
/// "Not a udp socket, instead socket protocol is: Protocol(6)");
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
fn demand_udp_socket(&self) -> anyhow::Result<()> {
|
||||
match self.socket_protocol() {
|
||||
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),
|
||||
|
||||
@@ -244,7 +244,6 @@
|
||||
use std::{borrow::Borrow, io};
|
||||
|
||||
use anyhow::ensure;
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
/// Generic trait for accessing [std::io::Error::kind]
|
||||
///
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! Utilities for encoding length-prefixed messages that can be transmitted via I/O streams.
|
||||
//!
|
||||
//! Messages are prefixed with an unsigned 64-bit little-endian length header, followed by the
|
||||
//! message payload. Each [`LengthPrefixEncoder`] maintains internal buffers and additional state for as-yet
|
||||
//! incomplete messages.
|
||||
//!
|
||||
//! It also performs sanity checks and handles typical error conditions that may be encountered
|
||||
//! when writing structured data to an output sink (such as stdout or an active socket connection).
|
||||
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cmp::min,
|
||||
@@ -9,31 +18,32 @@ use zeroize::Zeroize;
|
||||
|
||||
use crate::{io::IoResultKindHintExt, result::ensure_or};
|
||||
|
||||
/// Size of the length prefix header in bytes - equal to the size of a u64
|
||||
/// 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>();
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
#[error("Write position is out of buffer bounds")]
|
||||
/// Error type indicating that a write position is beyond the boundaries of the allocated buffer
|
||||
/// Error indicating that the given offset exceeds the bounds of the allocated buffer.
|
||||
pub struct PositionOutOfBufferBounds;
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
#[error("Write position is out of message bounds")]
|
||||
/// Error type indicating that a write position is beyond the boundaries of the message
|
||||
/// Error indicating that the given offset exceeds the bounds of the message.
|
||||
pub struct PositionOutOfMessageBounds;
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
#[error("Write position is out of header bounds")]
|
||||
/// Error type indicating that a write position is beyond the boundaries of the header
|
||||
/// Error indicating that the given offset exceeds the bounds of the header.
|
||||
pub struct PositionOutOfHeaderBounds;
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
#[error("Message length is bigger than buffer length")]
|
||||
/// Error type indicating that the message length is larger than the available buffer space
|
||||
/// Error indicating that the message size is larger than the available buffer space.
|
||||
pub struct MessageTooLarge;
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
/// Error type for message length sanity checks
|
||||
/// Error enum representing sanity check failures related to the message size.
|
||||
pub enum MessageLenSanityError {
|
||||
/// Error indicating position is beyond message boundaries
|
||||
#[error("{0:?}")]
|
||||
@@ -44,7 +54,7 @@ pub enum MessageLenSanityError {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
/// Error type for position bounds checking
|
||||
/// Error enum representing sanity check failures related to out-of-bounds memory access.
|
||||
pub enum PositionSanityError {
|
||||
/// Error indicating position is beyond message boundaries
|
||||
#[error("{0:?}")]
|
||||
@@ -55,7 +65,7 @@ pub enum PositionSanityError {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
/// Error type combining all sanity check errors
|
||||
/// Error enum representing sanity check failures of any kind.
|
||||
pub enum SanityError {
|
||||
/// Error indicating position is beyond message boundaries
|
||||
#[error("{0:?}")]
|
||||
@@ -101,7 +111,7 @@ impl From<PositionSanityError> for SanityError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a write operation on an IO stream
|
||||
/// Return type for `WriteToIo` operations, containing the number of bytes written and a completion flag.
|
||||
pub struct WriteToIoReturn {
|
||||
/// Number of bytes successfully written in this operation
|
||||
pub bytes_written: usize,
|
||||
@@ -110,7 +120,55 @@ pub struct WriteToIoReturn {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// Length-prefixed encoder that adds a length header to data before writing
|
||||
/// An encoder for length-prefixed messages.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Writing to output streams
|
||||
///
|
||||
/// Simplified usage example:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
|
||||
/// use rosenpass_util::length_prefix_encoding::encoder::HEADER_SIZE;
|
||||
///
|
||||
/// let message = String::from("hello world");
|
||||
/// let mut encoder = LengthPrefixEncoder::from_message(message.as_bytes());
|
||||
///
|
||||
/// let mut output = Vec::new();
|
||||
/// encoder.write_all_to_stdio(&mut output).expect("failed to write_all");
|
||||
///
|
||||
/// assert_eq!(output.len(), message.len() + HEADER_SIZE);
|
||||
///
|
||||
/// let (header, body) = output.split_at(HEADER_SIZE);
|
||||
/// let length = u64::from_le_bytes(header.try_into().unwrap());
|
||||
///
|
||||
/// assert_eq!(length as usize, message.len());
|
||||
/// assert_eq!(body, message.as_bytes());
|
||||
/// ```
|
||||
///
|
||||
/// For more examples, see also:
|
||||
///
|
||||
/// * [Self::write_all_to_stdio]
|
||||
/// * [Self::write_to_stdio]
|
||||
///
|
||||
///
|
||||
/// ## Basic error handling
|
||||
///
|
||||
/// Creating an encoder with invalid parameters triggers one of the various sanity checks:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, MessageLenSanityError};
|
||||
///
|
||||
/// let message_size = 32;
|
||||
/// let message = vec![0u8; message_size];
|
||||
///
|
||||
/// // The sanity check prevents an unsafe out-of-bounds access here
|
||||
/// let err = LengthPrefixEncoder::from_short_message(message, 2 * message_size)
|
||||
/// .expect_err("OOB access should fail");
|
||||
/// assert!(matches!(err, MessageLenSanityError::MessageTooLarge(_)));
|
||||
/// ```
|
||||
|
||||
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
|
||||
buf: Buf,
|
||||
header: [u8; HEADER_SIZE],
|
||||
@@ -135,6 +193,10 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
||||
}
|
||||
|
||||
/// Creates a new encoder using part of the buffer as a message
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See [Basic error handling](#basic-error-handling)
|
||||
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
|
||||
let mut r = Self::from_message(msg);
|
||||
r.set_message_len(len)?;
|
||||
@@ -149,12 +211,39 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
||||
}
|
||||
|
||||
/// Consumes the encoder and returns the underlying buffer
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
|
||||
///
|
||||
/// let msg = String::from("hello world");
|
||||
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
|
||||
/// let msg_buffer = encoder.into_buffer();
|
||||
/// assert_eq!(msg_buffer, msg.as_bytes());
|
||||
/// ```
|
||||
pub fn into_buffer(self) -> Buf {
|
||||
let Self { buf, .. } = self;
|
||||
buf
|
||||
}
|
||||
|
||||
/// Consumes the encoder and returns buffer, message length and write position
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
|
||||
///
|
||||
/// let msg = String::from("hello world");
|
||||
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
|
||||
/// assert!(encoder.encoded_message_bytes() > msg.len());
|
||||
/// assert!(!encoder.exhausted());
|
||||
///
|
||||
/// let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
|
||||
/// assert_eq!(msg_buffer, msg.as_bytes());
|
||||
/// assert_eq!(write_offset, 0);
|
||||
/// assert_eq!(msg_length, msg.len());
|
||||
/// ```
|
||||
pub fn into_parts(self) -> (Buf, usize, usize) {
|
||||
let len = self.message_len();
|
||||
let pos = self.writing_position();
|
||||
@@ -169,6 +258,25 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
||||
}
|
||||
|
||||
/// Writes the full message to an IO writer, retrying on interrupts
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, HEADER_SIZE};
|
||||
/// let msg = String::from("message in a bottle");
|
||||
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
|
||||
///
|
||||
/// let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
|
||||
///
|
||||
/// // Fast-forward - behaves as if the HEADER had already been written; only the message remains
|
||||
/// encoder
|
||||
/// .set_header_offset(HEADER_SIZE)
|
||||
/// .expect("failed to move cursor");
|
||||
/// let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
|
||||
/// encoder.write_all_to_stdio(&mut sink).expect("write failed");
|
||||
/// assert_eq!(&sink.get_ref()[0..msg.len()], msg.as_bytes());
|
||||
/// ```
|
||||
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
|
||||
use io::ErrorKind as K;
|
||||
loop {
|
||||
@@ -185,7 +293,45 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status
|
||||
/// Attempts to write the next chunk of data to an IO writer, returning the number of bytes written and completion flag
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::io::Cursor;
|
||||
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, WriteToIoReturn, HEADER_SIZE};
|
||||
/// let msg = String::from("Hello world");
|
||||
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
|
||||
///
|
||||
/// let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
|
||||
/// assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
|
||||
/// assert!(!encoder.exhausted());
|
||||
///
|
||||
/// let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
|
||||
///
|
||||
/// loop {
|
||||
/// let result: WriteToIoReturn = encoder
|
||||
/// .write_to_stdio(&mut dummy_stdout)
|
||||
/// .expect("write failed");
|
||||
/// if dummy_stdout.position() as usize >= prefixed_msg_size {
|
||||
/// // The entire message should've been written (and the encoder state reflect this)
|
||||
/// assert!(result.done);
|
||||
/// assert_eq!(result.bytes_written, msg.len());
|
||||
/// assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
|
||||
/// assert_eq!(encoder.message_written(), msg.as_bytes());
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
/// let buffer_bytes = dummy_stdout.get_ref();
|
||||
/// match String::from_utf8(buffer_bytes.to_vec()) {
|
||||
/// Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
|
||||
/// Err(err) => println!("Error converting buffer to String: {:?}", err),
|
||||
/// }
|
||||
/// assert_eq!(
|
||||
/// &dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
|
||||
/// msg.as_bytes()
|
||||
/// );
|
||||
/// ```
|
||||
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
|
||||
if self.exhausted() {
|
||||
return Ok(WriteToIoReturn {
|
||||
@@ -438,3 +584,150 @@ impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixEncoder<Buf> {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_lpe_error_conversion_upcast_valid() {
|
||||
let len_error = MessageTooLarge;
|
||||
let len_sanity_error: MessageLenSanityError = len_error.into();
|
||||
|
||||
let sanity_error: SanityError = len_error.into();
|
||||
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
|
||||
let sanity_error: SanityError = len_sanity_error.into();
|
||||
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
|
||||
|
||||
let pos_error = PositionOutOfBufferBounds;
|
||||
let pos_sanity_error: PositionSanityError = pos_error.into();
|
||||
|
||||
let sanity_error: SanityError = pos_error.into();
|
||||
assert!(matches!(
|
||||
sanity_error,
|
||||
SanityError::PositionOutOfBufferBounds(_)
|
||||
));
|
||||
|
||||
let sanity_error: SanityError = pos_sanity_error.into();
|
||||
assert!(matches!(
|
||||
sanity_error,
|
||||
SanityError::PositionOutOfBufferBounds(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lpe_error_conversion_downcast_invalid() {
|
||||
let pos_error = PositionOutOfBufferBounds;
|
||||
let sanity_error = SanityError::PositionOutOfBufferBounds(pos_error.into());
|
||||
match MessageLenSanityError::try_from(sanity_error) {
|
||||
Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"),
|
||||
Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_to_stdio_cursor() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let msg = String::from("Hello world");
|
||||
let prefixed_msg_size = msg.len() + HEADER_SIZE;
|
||||
|
||||
let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
|
||||
assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
|
||||
assert!(!encoder.exhausted());
|
||||
|
||||
let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
|
||||
|
||||
loop {
|
||||
let result: WriteToIoReturn = encoder
|
||||
.write_to_stdio(&mut dummy_stdout)
|
||||
.expect("write failed");
|
||||
if dummy_stdout.position() as usize >= prefixed_msg_size {
|
||||
// The entire message should've been written (and the encoder state reflect this)
|
||||
assert!(result.done);
|
||||
assert_eq!(result.bytes_written, msg.len());
|
||||
assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
|
||||
assert_eq!(encoder.message_written(), msg.as_bytes());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let buffer_bytes = dummy_stdout.get_ref();
|
||||
match String::from_utf8(buffer_bytes.to_vec()) {
|
||||
Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
|
||||
Err(err) => println!("Error converting buffer to String: {:?}", err),
|
||||
}
|
||||
assert_eq!(
|
||||
&dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
|
||||
msg.as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_offset_header() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let mut msg = Vec::<u8>::new();
|
||||
msg.extend_from_slice(b"cats");
|
||||
msg.extend_from_slice(b" and dogs");
|
||||
let msg_len = msg.len();
|
||||
let prefixed_msg_size = msg_len + HEADER_SIZE;
|
||||
msg.extend_from_slice(b" and other animals"); // To be discarded
|
||||
|
||||
let mut encoder = LengthPrefixEncoder::from_short_message(msg.clone(), msg_len).unwrap();
|
||||
// Only the short message should have been stored (and the unused part discarded)
|
||||
assert_eq!(encoder.message_mut(), b"cats and dogs");
|
||||
assert_eq!(encoder.message_written_mut(), []);
|
||||
assert_eq!(encoder.message_left_mut(), b"cats and dogs");
|
||||
assert_eq!(encoder.buf_mut(), &msg);
|
||||
|
||||
// Fast-forward as if the header had already been sent - only the message remains
|
||||
encoder
|
||||
.set_header_offset(HEADER_SIZE)
|
||||
.expect("failed to move cursor");
|
||||
let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
|
||||
encoder.write_all_to_stdio(&mut sink).expect("write failed");
|
||||
assert_eq!(&sink.get_ref()[0..msg_len], &msg[0..msg_len]);
|
||||
|
||||
assert_eq!(encoder.message_mut(), b"cats and dogs");
|
||||
assert_eq!(encoder.message_written_mut(), b"cats and dogs");
|
||||
assert_eq!(encoder.message_left_mut(), []);
|
||||
assert_eq!(encoder.buf_mut(), &msg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_some_assembly_required() {
|
||||
let msg = String::from("hello world");
|
||||
let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
|
||||
assert!(encoder.encoded_message_bytes() > msg.len());
|
||||
assert!(!encoder.exhausted());
|
||||
|
||||
let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
|
||||
assert_eq!(msg_buffer, msg.as_bytes());
|
||||
assert_eq!(write_offset, 0);
|
||||
assert_eq!(msg_length, msg.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_restart_write_reset() {
|
||||
let msg = String::from("hello world");
|
||||
let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
|
||||
assert_eq!(encoder.writing_position(), 0);
|
||||
encoder.set_writing_position(4).unwrap();
|
||||
assert_eq!(encoder.writing_position(), 4);
|
||||
encoder.restart_write();
|
||||
assert_eq!(encoder.writing_position(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_state() {
|
||||
use zeroize::Zeroize;
|
||||
|
||||
let mut msg = Vec::<u8>::new();
|
||||
msg.extend_from_slice(b"test");
|
||||
let mut encoder = LengthPrefixEncoder::from_message(msg.clone());
|
||||
assert_eq!(encoder.message(), msg);
|
||||
encoder.zeroize();
|
||||
assert_eq!(encoder.message(), []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
/// Module that handles decoding functionality
|
||||
pub mod decoder;
|
||||
/// Module that handles encoding functionality
|
||||
pub mod encoder;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -11,6 +11,43 @@ use uds::UnixStreamExt as FdPassingExt;
|
||||
use crate::{repeat, return_if};
|
||||
|
||||
/// A structure that facilitates writing data and file descriptors to a Unix domain socket
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::io::{Read, Write};
|
||||
/// use std::net::UdpSocket;
|
||||
/// use std::os::fd::{AsFd, AsRawFd};
|
||||
///
|
||||
/// use mio::net::UnixStream;
|
||||
/// use rosenpass_util::mio::WriteWithFileDescriptors;
|
||||
///
|
||||
/// // Create socket descriptors that should be sent (not limited to UDP sockets)
|
||||
/// let peer_endpoint = "[::1]:0";
|
||||
/// let peer_socket = UdpSocket::bind(peer_endpoint).expect("bind failed");
|
||||
/// let peer_socket_fd = peer_socket.as_fd();
|
||||
/// let mut fds_to_send = vec![&peer_socket_fd].into();
|
||||
///
|
||||
/// // Create writable end (must be an Unix Domain Socket)
|
||||
/// // In this case, the readable end of the connection can be ignored
|
||||
/// let (mut dummy_sink, io_stream) = UnixStream::pair().expect("failed to create socket pair");
|
||||
/// let mut writable_stream = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(
|
||||
/// &io_stream, &mut fds_to_send);
|
||||
///
|
||||
/// // Send data and file descriptors (note that at least one byte should be written)
|
||||
/// writable_stream.write(&[0xffu8; 42]).expect("failed to write");
|
||||
/// // Discard data; the dummy_sink is only required to keep the connection alive here
|
||||
/// let mut recv_buffer = Vec::<u8>::new();
|
||||
/// dummy_sink.read(&mut recv_buffer[..]).expect("error reading from socket");
|
||||
/// writable_stream.flush().expect("failed to flush"); // Currently a NOOP
|
||||
///
|
||||
/// // The wrapped components can still be accessed
|
||||
/// let (socket, fds) = writable_stream.into_parts();
|
||||
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
|
||||
/// assert!(fds_to_send.is_empty(), "Failed to send file descriptors");
|
||||
///
|
||||
/// // Shutdown, cleanup, etc. goes here ...
|
||||
/// ```
|
||||
pub struct WriteWithFileDescriptors<Sock, Fd, 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
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
//! Client implementation for the WireGuard broker protocol.
|
||||
//!
|
||||
//! This module provides a client implementation that communicates with a WireGuard broker server
|
||||
//! using a binary protocol. The client handles serialization and deserialization of messages,
|
||||
//! error handling, and the core interaction flow.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use rosenpass_wireguard_broker::api::client::{BrokerClient, BrokerClientIo};
|
||||
//! #[derive(Debug)]
|
||||
//! struct MyIo;
|
||||
//!
|
||||
//! impl BrokerClientIo for MyIo {
|
||||
//! type SendError = std::io::Error;
|
||||
//! type RecvError = std::io::Error;
|
||||
//!
|
||||
//! fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
|
||||
//! // Implement sending logic
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
|
||||
//! // Implement receiving logic
|
||||
//! Ok(None)
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Create client with custom IO implementation
|
||||
//! let mut client = BrokerClient::new(MyIo);
|
||||
//! assert!(client.poll_response().unwrap().is_none());
|
||||
//! ```
|
||||
//!
|
||||
//! # Protocol
|
||||
//!
|
||||
//! The client implements a simple request-response protocol for setting WireGuard pre-shared keys.
|
||||
//! Messages are serialized using a binary format defined in the [`crate::api::msgs`] module.
|
||||
|
||||
use std::{borrow::BorrowMut, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
@@ -13,10 +51,13 @@ use super::{
|
||||
msgs::{Envelope, SetPskResponse},
|
||||
};
|
||||
|
||||
/// Error type for polling responses from the broker server.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientPollResponseError<RecvError> {
|
||||
/// An IO error occurred while receiving the response
|
||||
#[error(transparent)]
|
||||
IoError(RecvError),
|
||||
/// The received message was invalid or malformed
|
||||
#[error("Invalid message.")]
|
||||
InvalidMessage,
|
||||
}
|
||||
@@ -28,34 +69,52 @@ impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponse
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function that wraps a receive error into a `BrokerClientPollResponseError::IoError`
|
||||
fn io_poller<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
|
||||
BrokerClientPollResponseError::<RecvError>::IoError(e)
|
||||
}
|
||||
|
||||
/// Helper function that returns a `BrokerClientPollResponseError::InvalidMessage` error
|
||||
fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
|
||||
BrokerClientPollResponseError::<RecvError>::InvalidMessage
|
||||
}
|
||||
|
||||
/// Error type for setting pre-shared keys through the broker client.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientSetPskError<SendError> {
|
||||
/// Error encoding or decoding the message
|
||||
#[error("Error with encoding/decoding message")]
|
||||
MsgError,
|
||||
/// Error in the broker configuration
|
||||
#[error("Network Broker Config error: {0}")]
|
||||
BrokerError(NetworkBrokerConfigErr),
|
||||
/// IO error while sending the request
|
||||
#[error(transparent)]
|
||||
IoError(SendError),
|
||||
/// Interface name exceeds maximum length
|
||||
#[error("Interface name out of bounds")]
|
||||
IfaceOutOfBounds,
|
||||
}
|
||||
|
||||
/// Trait defining the IO operations required by the broker client.
|
||||
///
|
||||
/// Implementors must provide methods for sending and receiving binary messages.
|
||||
pub trait BrokerClientIo {
|
||||
/// Error type returned by send operations
|
||||
type SendError;
|
||||
/// Error type returned by receive operations
|
||||
type RecvError;
|
||||
|
||||
/// Send a binary message
|
||||
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
|
||||
/// Receive a binary message, returning None if no message is available
|
||||
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
|
||||
}
|
||||
|
||||
/// Client for interacting with a WireGuard broker server.
|
||||
///
|
||||
/// The client handles the protocol-level communication with the server,
|
||||
/// including message serialization and response handling.
|
||||
#[derive(Debug)]
|
||||
pub struct BrokerClient<Io>
|
||||
where
|
||||
@@ -68,18 +127,37 @@ impl<Io> BrokerClient<Io>
|
||||
where
|
||||
Io: BrokerClientIo + Debug,
|
||||
{
|
||||
/// Creates a new `BrokerClient` with the given IO implementation.
|
||||
pub fn new(io: Io) -> Self {
|
||||
Self { io }
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying IO implementation.
|
||||
pub fn io(&self) -> &Io {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying IO implementation.
|
||||
pub fn io_mut(&mut self) -> &mut Io {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// Polls for a response from the broker server.
|
||||
///
|
||||
/// This method attempts to receive and parse a SetPsk response message from the server.
|
||||
/// If no message is available, returns `Ok(None)`. If a message is received, it is
|
||||
/// parsed and validated before being returned as `Ok(Some(result))`.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(Some(result))` if a valid response was received
|
||||
/// - `Ok(None)` if no message was available
|
||||
/// - `Err(BrokerClientPollResponseError)` if an error occurred during receiving or parsing
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - An IO error occurs while receiving the message
|
||||
/// - The received message is invalid or malformed
|
||||
/// - The message type is incorrect
|
||||
pub fn poll_response(
|
||||
&mut self,
|
||||
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
|
||||
@@ -147,3 +225,112 @@ where
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use msgs::{MsgType, SetPskError, SetPskResponseReturnCode};
|
||||
|
||||
// Mock IO implementation for testing
|
||||
#[derive(Debug)]
|
||||
struct MockIo {
|
||||
recv_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MockIo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
recv_data: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_recv_data(&mut self, data: Option<Vec<u8>>) {
|
||||
self.recv_data = data.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
impl BrokerClientIo for MockIo {
|
||||
type SendError = std::io::Error;
|
||||
type RecvError = std::io::Error;
|
||||
|
||||
fn send_msg(&mut self, _buf: &[u8]) -> Result<(), Self::SendError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
|
||||
if self.recv_data.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(&self.recv_data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_response_msg(return_code: u8) -> Vec<u8> {
|
||||
let mut msg = vec![
|
||||
MsgType::SetPsk as u8, // msg_type
|
||||
0,
|
||||
0,
|
||||
0, // reserved bytes
|
||||
];
|
||||
msg.push(return_code); // return_code
|
||||
msg
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_response_no_message() {
|
||||
let io = MockIo::new();
|
||||
let mut client = BrokerClient::new(io);
|
||||
|
||||
assert_eq!(client.poll_response().unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_response_success() {
|
||||
let mut io = MockIo::new();
|
||||
io.set_recv_data(Some(create_response_msg(
|
||||
SetPskResponseReturnCode::Success as u8,
|
||||
)));
|
||||
let mut client = BrokerClient::new(io);
|
||||
|
||||
assert_eq!(client.poll_response().unwrap(), Some(Ok(())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_response_no_such_peer() {
|
||||
let mut io = MockIo::new();
|
||||
io.set_recv_data(Some(create_response_msg(
|
||||
SetPskResponseReturnCode::NoSuchPeer as u8,
|
||||
)));
|
||||
let mut client = BrokerClient::new(io);
|
||||
|
||||
assert_eq!(
|
||||
client.poll_response().unwrap(),
|
||||
Some(Err(SetPskError::NoSuchPeer))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_response_invalid_message_type() {
|
||||
let mut io = MockIo::new();
|
||||
io.set_recv_data(Some(vec![0xFF, 0, 0, 0, 0])); // Invalid message type
|
||||
let mut client = BrokerClient::new(io);
|
||||
|
||||
assert!(matches!(
|
||||
client.poll_response(),
|
||||
Err(BrokerClientPollResponseError::InvalidMessage)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_response_invalid_return_code() {
|
||||
let mut io = MockIo::new();
|
||||
io.set_recv_data(Some(create_response_msg(0xFF))); // Invalid return code
|
||||
let mut client = BrokerClient::new(io);
|
||||
|
||||
assert!(matches!(
|
||||
client.poll_response(),
|
||||
Err(BrokerClientPollResponseError::InvalidMessage)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! This module provides [NetworkBrokerConfig] for configuring a
|
||||
//! [BrokerServer](crate::api::server::BrokerServer) and tooling to serialize and deserialize these
|
||||
//! configurations.
|
||||
|
||||
use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
|
||||
use derive_builder::Builder;
|
||||
use rosenpass_secret_memory::{Public, Secret};
|
||||
@@ -5,13 +9,19 @@ use rosenpass_secret_memory::{Public, Secret};
|
||||
#[derive(Builder, Debug)]
|
||||
#[builder(pattern = "mutable")]
|
||||
//TODO: Use generics for iface, add additional params
|
||||
/// Specifies a configuration for a [BrokerServer](crate::api::server::BrokerServer).
|
||||
pub struct NetworkBrokerConfig<'a> {
|
||||
/// The interface for the [BrokerServer](crate::api::server::BrokerServer).
|
||||
pub iface: &'a str,
|
||||
/// The peer identifier for the [BrokerServer](crate::api::server::BrokerServer).
|
||||
pub peer_id: &'a Public<WG_PEER_LEN>,
|
||||
/// The pre-shared key for the [BrokerServer](crate::api::server::BrokerServer) and the
|
||||
/// interface.
|
||||
pub psk: &'a Secret<WG_KEY_LEN>,
|
||||
}
|
||||
|
||||
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
|
||||
/// Transforms a [NetworkBrokerConfig] into a [SerializedBrokerConfig] meant for serialization.
|
||||
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
|
||||
Self {
|
||||
interface: src.iface.as_bytes(),
|
||||
@@ -22,15 +32,23 @@ impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error variants that can occur when loading a [NetworkBrokerConfig] from a
|
||||
/// [SerializedBrokerConfig].
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum NetworkBrokerConfigErr {
|
||||
/// Error indicating that the interface specification could not be read correctly.
|
||||
#[error("Interface")]
|
||||
Interface,
|
||||
Interface, // TODO, give this a better name.
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
|
||||
type Error = NetworkBrokerConfigErr;
|
||||
|
||||
/// Tries to load a [NetworkBrokerConfig] from a [SerializedBrokerConfig].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [NetworkBrokerConfigErr::Interface]-error when the interface description
|
||||
/// can not be parsed correctly.
|
||||
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
|
||||
let iface =
|
||||
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
//! This module implements the binary WireGuard broker protocol in the form of the [client::BrokerClient]
|
||||
//! and the [server::BrokerServer].
|
||||
//!
|
||||
//! Specifically, The protocol enables the client to tell the server to set a pre-shared key for a
|
||||
//! wireguard interface.
|
||||
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod msgs;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
//! This module defines message formats for messages in the Wireguard Broker protocol as well as
|
||||
//! helper structures like errors and conversion functions.
|
||||
|
||||
use std::str::{from_utf8, Utf8Error};
|
||||
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
/// The number of bytes reserved for overhead when packaging data.
|
||||
pub const ENVELOPE_OVERHEAD: usize = 1 + 3;
|
||||
|
||||
/// The buffer size for request messages.
|
||||
pub const REQUEST_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 32 + 32 + 1 + 255;
|
||||
/// The buffer size for responses.
|
||||
pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1;
|
||||
|
||||
/// Envelope for messages being passed around.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
@@ -17,25 +25,43 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
pub payload: M,
|
||||
}
|
||||
|
||||
/// Message format for requests to set a pre-shared key.
|
||||
/// # Example
|
||||
///
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct SetPskRequest {
|
||||
/// The pre-shared key.
|
||||
pub psk: [u8; 32],
|
||||
/// The identifier of the peer.
|
||||
pub peer_id: [u8; 32],
|
||||
/// The size for the interface
|
||||
pub iface_size: u8, // TODO: We should have variable length strings in lenses
|
||||
/// The buffer for the interface.
|
||||
pub iface_buf: [u8; 255],
|
||||
}
|
||||
|
||||
impl SetPskRequest {
|
||||
/// Gets the interface specification as byte slice.
|
||||
pub fn iface_bin(&self) -> &[u8] {
|
||||
let len = self.iface_size as usize;
|
||||
&self.iface_buf[..len]
|
||||
}
|
||||
|
||||
/// Gets the interface specification as a `&str`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [Utf8Error] if the interface specification isn't utf8 encoded.
|
||||
pub fn iface(&self) -> Result<&str, Utf8Error> {
|
||||
from_utf8(self.iface_bin())
|
||||
}
|
||||
|
||||
/// Sets the interface specification to `iface`. No check is made whether `iface` is correctly
|
||||
/// encoded as utf8.
|
||||
///
|
||||
/// # Result
|
||||
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
|
||||
/// [Some(())](Some).
|
||||
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
|
||||
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
|
||||
|
||||
@@ -47,17 +73,24 @@ impl SetPskRequest {
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Sets the interface specification to `iface`.
|
||||
///
|
||||
/// # Result
|
||||
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
|
||||
/// [Some(())](Some).
|
||||
pub fn set_iface(&mut self, iface: &str) -> Option<()> {
|
||||
self.set_iface_bin(iface.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Message format for response to the set pre-shared key operation.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct SetPskResponse {
|
||||
pub return_code: u8,
|
||||
}
|
||||
|
||||
/// Error type for the errors that can occur when setting a pre-shared key.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum SetPskError {
|
||||
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
|
||||
@@ -70,6 +103,12 @@ pub enum SetPskError {
|
||||
|
||||
pub type SetPskResult = Result<(), SetPskError>;
|
||||
|
||||
/// The return codes and their meanings for the set psk response operation.
|
||||
///
|
||||
/// [SetPskResponseReturnCode] is represented by by a single `u8` as required by the protocol.
|
||||
///
|
||||
/// # Example
|
||||
/// See [SetPskResponseReturnCode::try_from] for an example.
|
||||
#[repr(u8)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum SetPskResponseReturnCode {
|
||||
@@ -85,6 +124,19 @@ pub struct InvalidSetPskResponseError;
|
||||
impl TryFrom<u8> for SetPskResponseReturnCode {
|
||||
type Error = InvalidSetPskResponseError;
|
||||
|
||||
/// Parse a [u8] as a [MsgType].
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidSetPskResponseError, SetPskResponseReturnCode};
|
||||
/// let return_code: u8 = 0x00; // Usually specifically set or comes out of a message.
|
||||
/// let res = SetPskResponseReturnCode::try_from(return_code);
|
||||
/// assert!(res.is_ok());
|
||||
/// assert_eq!(res.unwrap(), SetPskResponseReturnCode::Success);
|
||||
/// # Ok::<(), InvalidSetPskResponseError>(())
|
||||
/// ```
|
||||
/// # Errors
|
||||
/// Returns a [InvalidSetPskResponseError] if `value` does not correspond to a known return code.
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use SetPskResponseReturnCode::*;
|
||||
match value {
|
||||
@@ -98,6 +150,9 @@ impl TryFrom<u8> for SetPskResponseReturnCode {
|
||||
}
|
||||
|
||||
impl From<SetPskResponseReturnCode> for SetPskResult {
|
||||
/// A [SetPskResult] can directly be deduced from a [SetPskResponseReturnCode].
|
||||
/// An [Ok] type is only returned if `value` is [SetPskResponseReturnCode::Success].
|
||||
/// Otherwise, an appropriate variant of [SetPskError] will be returned.
|
||||
fn from(value: SetPskResponseReturnCode) -> Self {
|
||||
use SetPskError as E;
|
||||
use SetPskResponseReturnCode as C;
|
||||
@@ -111,6 +166,7 @@ impl From<SetPskResponseReturnCode> for SetPskResult {
|
||||
}
|
||||
|
||||
impl From<SetPskResult> for SetPskResponseReturnCode {
|
||||
/// A [SetPskResponseReturnCode] can directly be deduced from a [SetPskResult].
|
||||
fn from(value: SetPskResult) -> Self {
|
||||
use SetPskError as E;
|
||||
use SetPskResponseReturnCode as C;
|
||||
@@ -123,18 +179,45 @@ impl From<SetPskResult> for SetPskResponseReturnCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// The types of messages supported by this crate. At the time of writing, this is only
|
||||
/// the message to set a pre-shared key.
|
||||
///
|
||||
/// [MsgType] is represented by a single `u8` as required by the protocol.
|
||||
///
|
||||
/// # Example
|
||||
/// It is usually used like this:
|
||||
/// ```
|
||||
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidMessageTypeError, MsgType};
|
||||
/// let typ: u8 = 0x01; // Usually specifically set or comes out of a message.
|
||||
/// let typ = MsgType::try_from(typ)?;
|
||||
/// let MsgType::SetPsk = typ; // Assert type.
|
||||
/// # Ok::<(), InvalidMessageTypeError>(())
|
||||
/// ```
|
||||
#[repr(u8)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum MsgType {
|
||||
SetPsk = 0x01,
|
||||
}
|
||||
|
||||
/// Error indicating that an invalid [MsgType] was used.
|
||||
/// This error is returned by [MsgType::try_from].
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct InvalidMessageTypeError;
|
||||
|
||||
impl TryFrom<u8> for MsgType {
|
||||
type Error = InvalidMessageTypeError;
|
||||
|
||||
/// Parse a [u8] as a [MsgType].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use rosenpass_wireguard_broker::api::msgs::MsgType;
|
||||
/// let msg_type = MsgType::try_from(0x01);
|
||||
/// assert!(msg_type.is_ok());
|
||||
/// assert_eq!(msg_type.unwrap(), MsgType::SetPsk);
|
||||
/// ```
|
||||
/// # Errors
|
||||
/// Returns an [InvalidMessageTypeError] if `value` does not correspond to a valid [MsgType].
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x01 => Ok(MsgType::SetPsk),
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//! Server implementation for the WireGuard broker protocol.
|
||||
//!
|
||||
//! This module provides a server implementation that communicates with WireGuard broker clients
|
||||
//! using a binary protocol. The server handles serialization and deserialization of messages,
|
||||
//! error handling, and the core interaction flow.
|
||||
//! Specifically, it handles requests to set a pre-shared key for a wireguard interface.
|
||||
|
||||
use std::borrow::BorrowMut;
|
||||
|
||||
use rosenpass_secret_memory::{Public, Secret};
|
||||
@@ -7,12 +14,16 @@ use crate::WireGuardBroker;
|
||||
|
||||
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
|
||||
|
||||
/// Error variants for the [BrokerServer].
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerServerError {
|
||||
/// Indicates that an unknown request type was encountered.
|
||||
#[error("No such request type: {}", .0)]
|
||||
NoSuchRequestType(u8),
|
||||
/// Indicates that an invalid message was sent.
|
||||
#[error("Invalid message received.")]
|
||||
InvalidMessage,
|
||||
/// Indicates an error when configuration the network broker.
|
||||
#[error("Network Broker Config error: {0}")]
|
||||
BrokerError(NetworkBrokerConfigErr),
|
||||
}
|
||||
@@ -24,11 +35,18 @@ impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
|
||||
}
|
||||
}
|
||||
|
||||
/// The broker server. It requires an inner [WireGuardBroker] and an error type such
|
||||
/// that the [msgs::SetPskError] implements [From] for the error type.
|
||||
/// # Type Parameters
|
||||
/// - `Err`: The used error type. Must be chosen such that [msgs::SetPskError] implements
|
||||
/// [`From<Err>`](From)
|
||||
///- `Inner`: A [WireGuardBroker]-type parametrized with `Err`.
|
||||
pub struct BrokerServer<Err, Inner>
|
||||
where
|
||||
Inner: WireGuardBroker<Error = Err>,
|
||||
msgs::SetPskError: From<Err>,
|
||||
{
|
||||
/// The inner [WireGuardBroker].
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
@@ -38,10 +56,17 @@ where
|
||||
msgs::SetPskError: From<Err>,
|
||||
Err: std::fmt::Debug,
|
||||
{
|
||||
/// Creates a new [BrokerServer] from a [WireGuardBroker].
|
||||
pub fn new(inner: Inner) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Processes a message (at the moment only setting the pre-shared key is supported)
|
||||
/// and takes the appropriate actions.
|
||||
///
|
||||
/// # Errors
|
||||
/// - [BrokerServerError::InvalidMessage] if the message is not properly formatted or refers to
|
||||
/// an unsupported message type.
|
||||
pub fn handle_message(
|
||||
&mut self,
|
||||
req: &[u8],
|
||||
@@ -53,22 +78,28 @@ where
|
||||
let typ = msgs::MsgType::try_from(*typ)?;
|
||||
let msgs::MsgType::SetPsk = typ; // Assert type
|
||||
|
||||
let req = zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req)
|
||||
.ok_or(BrokerServerError::InvalidMessage)?;
|
||||
let mut res = zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res)
|
||||
.ok_or(BrokerServerError::InvalidMessage)?;
|
||||
let req =
|
||||
zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req).ok_or(InvalidMessage)?;
|
||||
let mut res =
|
||||
zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res).ok_or(InvalidMessage)?;
|
||||
res.msg_type = msgs::MsgType::SetPsk as u8;
|
||||
self.handle_set_psk(&req.payload, &mut res.payload)?;
|
||||
|
||||
Ok(res.bytes().len())
|
||||
}
|
||||
|
||||
/// Sets the pre-shared key for the interface identified in `req` to the pre-shared key
|
||||
/// specified in `req`.
|
||||
///
|
||||
/// # Errors
|
||||
/// - [InvalidMessage](BrokerServerError::InvalidMessage) if the `iface` specified in `req` is
|
||||
/// longer than 255 bytes or not correctly encoded in utf8.
|
||||
fn handle_set_psk(
|
||||
&mut self,
|
||||
req: &SetPskRequest,
|
||||
res: &mut SetPskResponse,
|
||||
) -> Result<(), BrokerServerError> {
|
||||
// Using unwrap here since lenses can not return fixed-size arrays
|
||||
// Using unwrap here since lenses can not return fixed-size arrays.
|
||||
// TODO: Slices should give access to fixed size arrays
|
||||
let peer_id = Public::from_slice(&req.peer_id);
|
||||
let psk = Secret::from_slice(&req.psk);
|
||||
@@ -95,3 +126,60 @@ where
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// We can only include this test if this feature is enabled, because otherwise
|
||||
// brokers::netlink::SetPskError is not defined.
|
||||
#[cfg(all(feature = "experiment_api", target_os = "linux"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::msgs;
|
||||
use crate::api::msgs::{Envelope, SetPskRequest};
|
||||
use crate::api::server::BrokerServer;
|
||||
use crate::brokers::netlink::SetPskError;
|
||||
use crate::{SerializedBrokerConfig, WireGuardBroker};
|
||||
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MockWireGuardBroker {
|
||||
psk: Secret<32>,
|
||||
}
|
||||
|
||||
impl WireGuardBroker for MockWireGuardBroker {
|
||||
type Error = SetPskError;
|
||||
|
||||
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error> {
|
||||
self.psk = config.psk.clone();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_broker_server() {
|
||||
secret_policy_use_only_malloc_secrets();
|
||||
let mock_broker = MockWireGuardBroker {
|
||||
psk: Secret::zero(),
|
||||
};
|
||||
let mut broker_server = BrokerServer::new(mock_broker);
|
||||
let mut iface_buf: [u8; 255] = [0; 255];
|
||||
// These are the utf encoded bytes of the string "wg0".
|
||||
iface_buf[0] = 119;
|
||||
iface_buf[1] = 103;
|
||||
iface_buf[2] = 48;
|
||||
let psk_req = SetPskRequest {
|
||||
psk: [0; 32],
|
||||
peer_id: [0; 32],
|
||||
iface_size: 3,
|
||||
iface_buf,
|
||||
};
|
||||
let req: Envelope<SetPskRequest> = Envelope {
|
||||
msg_type: 0x01, // The only valid value.
|
||||
reserved: [0, 0, 0],
|
||||
payload: psk_req,
|
||||
};
|
||||
let mut res: [u8; msgs::RESPONSE_MSG_BUFFER_SIZE] = [0; msgs::RESPONSE_MSG_BUFFER_SIZE];
|
||||
broker_server
|
||||
.handle_message(req.as_bytes(), &mut res)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! This module defines the Unix socket broker that interacts with the Linux-specific
|
||||
//! WireGuard broker through a privileged process.
|
||||
//!
|
||||
//! It manages communication using length-prefixed
|
||||
//! messages that are read from standard-input.
|
||||
//! On each input message the process responds through its standard-output
|
||||
//!
|
||||
//! The functionality is only supported on Linux systems.
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::main().unwrap();
|
||||
@@ -8,20 +17,33 @@ fn main() {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux {
|
||||
//! Linux-specific implementation for the broker that communicates with the WireGuard broker.
|
||||
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
|
||||
use rosenpass_wireguard_broker::api::msgs;
|
||||
use rosenpass_wireguard_broker::api::server::BrokerServer;
|
||||
use rosenpass_wireguard_broker::brokers::netlink as wg;
|
||||
|
||||
/// Represents errors that can occur during WireGuard broker operations
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum BrokerAppError {
|
||||
/// Wraps standard I/O errors that may occur during broker operations
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
/// Wraps WireGuard connection errors
|
||||
#[error(transparent)]
|
||||
WgConnectError(#[from] wg::ConnectError),
|
||||
|
||||
/// Wraps errors that occur when setting WireGuard Pre-Shared Keys (PSK)
|
||||
#[error(transparent)]
|
||||
WgSetPskError(#[from] wg::SetPskError),
|
||||
|
||||
/// Indicates that a received message exceeds the maximum allowed size
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `u64` - The size of the oversized message in bytes
|
||||
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
|
||||
OversizedMessage(u64),
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user