Compare commits

..

6 Commits

Author SHA1 Message Date
Karolin Varner
6b3bebb9b9 fix: CI issues under Darwin 2024-12-08 13:57:43 +01:00
Jacek Galowicz
9948169127 rp systemd unit file: introduce and test 2024-12-08 09:43:35 +01:00
Jacek Galowicz
eced56bd70 rp: Add exchange-config command
This is similar to `rosenpass exchange`/`rosenpass exchange-config`.
It's however slightly different to the configuration file models the `rp
exchange` command line.
2024-12-08 09:43:35 +01:00
Jacek Galowicz
df1e195b5d rp: set allowed-ips as routes
Prepare the rp app for a systemd unit file that sets up wireguard
connections.
2024-12-08 09:43:35 +01:00
Jacek Galowicz
e1e280c4c5 rp: Add ip parameter to exchange command
Prepare the `rp` app for a systemd unit that sets up a wireguard connection.
2024-12-08 09:43:35 +01:00
Jacek Galowicz
06cd178977 rosenpass systemd unit file: introduce and test 2024-12-08 09:43:35 +01:00
72 changed files with 532 additions and 6198 deletions

View File

@@ -194,19 +194,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup default nightly
- run: rustup component add llvm-tools-preview
- run: |
cargo install cargo-llvm-cov || true
cargo install grcov || true
./coverage_report.sh
cargo llvm-cov \
--workspace\
--all-features \
--lcov \
--output-path coverage.lcov
# If using tarapulin
#- run: cargo install cargo-tarpaulin
#- run: cargo tarpaulin --out Xml
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
files: ./target/grcov/lcov
files: ./coverage.lcov
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,41 +1,38 @@
# Contributing to Rosenpass
**Making a new Release of Rosenpass — Cooking Recipe**
## Common operations
If you have to change a file, do what it takes to get the change as commit on the main branch, then **start from step 0**.
If any other issue occurs
### Apply code formatting
0. Make sure you are in the root directory of the project
- `cd "$(git rev-parse --show-toplevel)"`
1. Make sure you locally checked out the head of the main branch
- `git stash --include-untracked && git checkout main && git pull`
2. Make sure all tests pass
- `cargo test --workspace --all-features`
3. Make sure the current version in `rosenpass/Cargo.toml` matches that in the [last release on GitHub](https://github.com/rosenpass/rosenpass/releases)
- Only normal releases count, release candidates and draft releases can be ignored
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
- See `cargo release --help` for more information on the available release types
- Pick `rc` if in doubt
5. Try to release a new version
- `cargo release rc --package rosenpass`
- An issue was reported? Go fix it, start again with step 0!
6. Actually make the release
- `cargo release rc --package rosenpass --execute`
- Tentatively wait for any interactions, such as entering ssh keys etc.
- You may be asked for your ssh key multiple times!
Format rust code:
**Frequently Asked Questions (FAQ)**
```bash
cargo fmt
```
Format rust code in markdown files:
```bash
./format_rust_code.sh --mode fix
```
### Spawn a development environment with nix
```bash
nix develop .#fullEnv
```
You need to [install this nix package manager](https://wiki.archlinux.org/title/Nix) first.
### Run our test
Make sure to increase the stack size available; some of our cryptography operations require a lot of stack memory.
```bash
RUST_MIN_STACK=8388608 cargo test --workspace --all-features
```
### Generate coverage reports
Keep in mind that many of Rosenpass' tests are doctests, so to get an accurate read on our code coverage, you have to include doctests:
```bash
./coverage_report.sh
```
- You have untracked files, which `cargo release` complains about?
- `git stash --include-untracked`
- You cannot push to crates.io because you are not logged in?
- Follow the steps displayed in [`cargo login`](https://doc.rust-lang.org/cargo/commands/cargo-login.html)
- How is the release page added to [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) itself?
- Our CI Pipeline will create the release, once `cargo release` pushed the new version tag to the repo. The new release should pop up almost immediately in [GitHub Releases](https://github.com/rosenpass/rosenpass/releases) after the [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml) pipeline started.
- No new release pops up in the `Release` sidebar element on the [main page](https://github.com/rosenpass/rosenpass)
- Did you push a `rc` release? This view only shows non-draft release, but `rc` releases are considered as draft. See [Releases](https://github.com/rosenpass/rosenpass/releases) page to see all (including draft!) releases.
- The release page was created on GitHub, but there are no assets/artifacts other than the source code tar ball/zip?
- The artifacts are generated and pushed automatically to the release, but this takes some time (a couple of minutes). You can check the respective CI pipeline: [Actions/Release](https://github.com/rosenpass/rosenpass/actions/workflows/release.yaml), which should start immediately after `cargo release` pushed the new release tag to the repo. The release artifacts only are added later to the release, once all jobs in bespoke pipeline finished.
- How are the release artifacts generated, and what are they?
- The release artifacts are built using one Nix derivation per platform, `nix build .#release-package`. It contains both statically linked versions of `rosenpass` itself and OCI container images.

66
Cargo.lock generated
View File

@@ -381,9 +381,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.23"
version = "4.5.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b"
dependencies = [
"clap_builder",
"clap_derive",
@@ -391,23 +391,23 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.23"
version = "4.5.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1"
dependencies = [
"anstream",
"anstyle",
"clap_lex 0.7.4",
"clap_lex 0.7.2",
"strsim 0.11.1",
]
[[package]]
name = "clap_complete"
version = "4.5.40"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
dependencies = [
"clap 4.5.23",
"clap 4.5.22",
]
[[package]]
@@ -433,9 +433,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.4"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "clap_mangen"
@@ -443,7 +443,7 @@ version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf"
dependencies = [
"clap 4.5.23",
"clap 4.5.22",
"roff",
]
@@ -815,12 +815,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.10"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -1204,9 +1204,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.168"
version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libcrux"
@@ -1823,7 +1823,7 @@ name = "rosenpass"
version = "0.3.0-dev"
dependencies = [
"anyhow",
"clap 4.5.23",
"clap 4.5.22",
"clap_complete",
"clap_mangen",
"command-fds",
@@ -1850,7 +1850,6 @@ dependencies = [
"rustix",
"serde",
"serial_test",
"signal-hook",
"stacker",
"static_assertions",
"tempfile",
@@ -1865,11 +1864,6 @@ dependencies = [
[[package]]
name = "rosenpass-cipher-traits"
version = "0.1.0"
dependencies = [
"anyhow",
"rosenpass-oqs",
"rosenpass-secret-memory",
]
[[package]]
name = "rosenpass-ciphers"
@@ -1970,7 +1964,7 @@ name = "rosenpass-wireguard-broker"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.5.23",
"clap 4.5.22",
"derive_builder 0.20.2",
"env_logger",
"libc",
@@ -2059,15 +2053,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.42"
version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -2114,18 +2108,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.216"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
@@ -2184,16 +2178,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"

View File

@@ -47,10 +47,10 @@ memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec
rand = "0.8.5"
typenum = "1.17.0"
log = { version = "0.4.22" }
clap = { version = "4.5.23", features = ["derive"] }
clap = { version = "4.5.22", features = ["derive"] }
clap_mangen = "0.2.24"
clap_complete = "4.5.40"
serde = { version = "1.0.216", features = ["derive"] }
clap_complete = "4.5.38"
serde = { version = "1.0.215", features = ["derive"] }
arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
mio = { version = "1.0.3", features = ["net", "os-poll"] }
@@ -74,7 +74,6 @@ hex = { version = "0.4.3" }
heck = { version = "0.5.0" }
libc = { version = "0.2" }
uds = { git = "https://github.com/rosenpass/uds" }
signal-hook = "0.3.17"
#Dev dependencies
serial_test = "3.2.0"
@@ -90,4 +89,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.42", features = ["net", "fs", "process"] }
rustix = { version = "0.38.41", features = ["net", "fs"] }

View File

@@ -10,8 +10,3 @@ repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
[dev-dependencies]
rosenpass-oqs = { workspace = true }
rosenpass-secret-memory = { workspace = true }
anyhow = {workspace = true}

View File

@@ -2,4 +2,4 @@
Rosenpass internal library providing traits for cryptographic primitives.
This is an internal library; no guarantee is made about its API at this point in time.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -5,128 +5,10 @@
//!
//! 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:
//! [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>(())
//!```
//!
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
/// Key Encapsulation Mechanism
///

View File

@@ -2,192 +2,100 @@ use anyhow::Result;
use rosenpass_secret_memory::Secret;
use rosenpass_to::To;
use crate::keyed_hash as hash;
use crate::subtle::incorrect_hmac_blake2b as hash;
pub use hash::KEY_LEN;
///
///```rust
/// # use rosenpass_ciphers::hash_domain::{HashDomain, HashDomainNamespace, SecretHashDomain, SecretHashDomainNamespace};
/// use rosenpass_secret_memory::Secret;
/// # rosenpass_secret_memory::secret_policy_use_only_malloc_secrets();
///
/// const PROTOCOL_IDENTIFIER: &str = "MY_PROTOCOL:IDENTIFIER";
/// // create use once hash domain for the protocol identifier
/// let mut hash_domain = HashDomain::zero();
/// hash_domain = hash_domain.mix(PROTOCOL_IDENTIFIER.as_bytes())?;
/// // upgrade to reusable hash domain
/// let hash_domain_namespace: HashDomainNamespace = hash_domain.dup();
/// // derive new key
/// let key_identifier = "my_key_identifier";
/// let key = hash_domain_namespace.mix(key_identifier.as_bytes())?.into_value();
/// // derive a new key based on a secret
/// const MY_SECRET_LEN: usize = 21;
/// let my_secret_bytes = "my super duper secret".as_bytes();
/// let my_secret: Secret<21> = Secret::from_slice("my super duper secret".as_bytes());
/// let secret_hash_domain: SecretHashDomain = hash_domain_namespace.mix_secret(my_secret)?;
/// // derive a new key based on the secret key
/// let new_key_identifier = "my_new_key_identifier".as_bytes();
/// let new_key = secret_hash_domain.mix(new_key_identifier)?.into_secret();
///
/// # Ok::<(), anyhow::Error>(())
///```
///
// TODO Use a proper Dec interface
/// A use-once hash domain for a specified key that can be used directly.
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
/// use [SecretHashDomain] instead.
#[derive(Clone, Debug)]
pub struct HashDomain([u8; KEY_LEN]);
/// A reusable hash domain for a namespace identified by the key.
/// The key must consist of [KEY_LEN] many bytes. If the key must remain secret,
/// use [SecretHashDomainNamespace] instead.
#[derive(Clone, Debug)]
pub struct HashDomainNamespace([u8; KEY_LEN]);
/// A use-once hash domain for a specified key that can be used directly
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
#[derive(Clone, Debug)]
pub struct SecretHashDomain(Secret<KEY_LEN>);
/// A reusable secure hash domain for a namespace identified by the key and that keeps the key secure
/// by wrapping it in [Secret]. The key must consist of [KEY_LEN] many bytes.
#[derive(Clone, Debug)]
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
impl HashDomain {
/// Creates a nw [HashDomain] initialized with a all-zeros key.
pub fn zero() -> Self {
Self([0u8; KEY_LEN])
}
/// Turns this [HashDomain] into a [HashDomainNamespace], keeping the key.
pub fn dup(self) -> HashDomainNamespace {
HashDomainNamespace(self.0)
}
/// Turns this [HashDomain] into a [SecretHashDomain] by wrapping the key into a [Secret]
/// and creating a new [SecretHashDomain] from it.
pub fn turn_secret(self) -> SecretHashDomain {
SecretHashDomain(Secret::from_slice(&self.0))
}
// TODO: Protocol! Use domain separation to ensure that
/// Creates a new [HashDomain] by mixing in a new key `v`. Specifically,
/// it evaluates [hash::hash] with this HashDomain's key as the key and `v`
/// as the `data` and uses the result as the key for the new [HashDomain].
///
pub fn mix(self, v: &[u8]) -> Result<Self> {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
}
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with this
/// [HashDomain]'s key as `k` and `v` as `d`.
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
/// Gets the key of this [HashDomain].
pub fn into_value(self) -> [u8; KEY_LEN] {
self.0
}
}
impl HashDomainNamespace {
/// Creates a new [HashDomain] by mixing in a new key `v`. Specifically,
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
/// as the `data` and uses the result as the key for the new [HashDomain].
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
Ok(HashDomain(
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
))
}
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
/// [HashDomainNamespace] as `k` and `v` as `d`.
///
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
}
impl SecretHashDomain {
/// Create a new [SecretHashDomain] with the given key `k` and data `d` by calling
/// [hash::hash] with `k` as the `key` and `d` s the `data`, and using the result
/// as the content for the new [SecretHashDomain].
/// Both `k` and `d` have to be exactly [KEY_LEN] bytes in length.
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
let mut r = SecretHashDomain(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?;
Ok(r)
}
/// Creates a new [SecretHashDomain] that is initialized with an all zeros key.
pub fn zero() -> Self {
Self(Secret::zero())
}
/// Turns this [SecretHashDomain] into a [SecretHashDomainNamespace].
pub fn dup(self) -> SecretHashDomainNamespace {
SecretHashDomainNamespace(self.0)
}
/// Creates a new [SecretHashDomain] from a [Secret] `k`.
///
/// It requires that `k` consist of exactly [KEY_LEN] bytes.
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k)
}
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
/// it evaluates [hash::hash] with this [SecretHashDomain]'s key as the key and `v`
/// as the `data` and uses the result as the key for the new [SecretHashDomain].
///
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v)
}
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
/// [HashDomainNamespace] as `k` and `v` as `d`.
///
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v.secret())
}
/// Get the secret key data from this [SecretHashDomain].
pub fn into_secret(self) -> Secret<KEY_LEN> {
self.0
}
/// Evaluate [hash::hash] with this [SecretHashDomain]'s data as the `key` and
/// `dst` as the `data` and stores the result as the new data for this [SecretHashDomain].
///
/// It requires that both `v` and `d` consist of exactly [KEY_LEN] many bytes.
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
hash::hash(v, dst).to(self.0.secret_mut())
}
}
impl SecretHashDomainNamespace {
/// Creates a new [SecretHashDomain] by mixing in a new key `v`. Specifically,
/// it evaluates [hash::hash] with the key of this HashDomainNamespace key as the key and `v`
/// as the `data` and uses the result as the key for the new [HashDomain].
///
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v)
}
/// Creates a new [SecretHashDomain] by mixing in a new key `v`
/// by calling [SecretHashDomain::invoke_primitive] with the key of this
/// [HashDomainNamespace] as `k` and `v` as `d`.
///
/// It requires that `v` consists of exactly [KEY_LEN] many bytes.
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
}
@@ -195,7 +103,6 @@ impl SecretHashDomainNamespace {
// TODO: This entire API is not very nice; we need this for biscuits, but
// it might be better to extract a special "biscuit"
// labeled subkey and reinitialize the chain with this
/// Get the secret key data from this [SecretHashDomain].
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
self.0
}

View File

@@ -2,25 +2,12 @@ use static_assertions::const_assert;
pub mod subtle;
/// All keyed primitives in this crate use 32 byte keys
pub const KEY_LEN: usize = 32;
const_assert!(KEY_LEN == aead::KEY_LEN);
const_assert!(KEY_LEN == xaead::KEY_LEN);
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
/// Keyed hashing
///
/// This should only be used for implementation details; anything with relevance
/// to the cryptographic protocol should use the facilities in [hash_domain], (though
/// hash domain uses this module internally)
pub mod keyed_hash {
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
};
}
/// Authenticated encryption with associated data
/// Chacha20poly1305 is used.
pub mod aead {
#[cfg(not(feature = "experiment_libcrux"))]
pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN};
@@ -31,7 +18,6 @@ pub mod aead {
}
/// Authenticated encryption with associated data with a constant nonce
/// XChacha20poly1305 is used.
pub mod xaead {
pub use crate::subtle::xchacha20poly1305_ietf::{
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
@@ -40,13 +26,6 @@ pub mod xaead {
pub mod hash_domain;
/// This crate includes two key encapsulation mechanisms.
/// Namely ClassicMceliece460896 (also referred to as `StaticKem` sometimes) and
/// Kyber512 (also referred to as `EphemeralKem` sometimes).
///
/// 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;
pub use rosenpass_oqs::Kyber512 as EphemeralKem;

View File

@@ -9,43 +9,19 @@ use blake2::Blake2bMac;
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]).
type Impl = Blake2bMac<U32>;
type KeyLen = <Impl as KeySizeUser>::KeySize;
type OutLen = <Impl as OutputSizeUser>::OutputSize;
/// The key length for BLAKE2b supported by this API. Currently 32 Bytes.
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.
pub const KEY_MIN: usize = KEY_LEN;
/// maximal key length supported by this API.
pub const KEY_MAX: usize = KEY_LEN;
/// minimal output length supported by this API.
pub const OUT_MIN: usize = OUT_LEN;
/// maximal output length supported by this API.
pub const OUT_MAX: usize = OUT_LEN;
/// 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
///
///```rust
/// # use rosenpass_ciphers::subtle::blake2b::hash;
/// use rosenpass_to::To;
/// let zero_key: [u8; 32] = [0; 32];
/// let data: [u8; 32] = [255; 32];
/// // buffer for the hash output
/// let mut hash_data: [u8; 32] = [0u8; 32];
///
/// assert!(hash(&zero_key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
///```
///
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
with_destination(|out: &mut [u8]| {
@@ -60,6 +36,7 @@ pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<(
let tmp = GenericArray::from_mut_slice(tmp.as_mut());
h.finalize_into(tmp);
copy_slice(tmp.as_ref()).to(out);
Ok(())
})
}

View File

@@ -6,39 +6,10 @@ use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::ChaCha20Poly1305 as AeadImpl;
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
/// The key length is 32 bytes or 256 bits.
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
/// The MAC tag length is 16 bytes or 128 bits.
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
/// The nonce length is 12 bytes or 96 bits.
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
/// Encrypts using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
/// `key` MUST be chosen (pseudo-)randomly and `nonce` MOST NOT be reused. The `key` slice MUST have
/// a length of [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
/// `plaintext.len()` + [TAG_LEN].
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
///
/// const PLAINTEXT_LEN: usize = 43;
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut ciphertext_buffer = [0u8;PLAINTEXT_LEN + TAG_LEN];
///
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
/// assert!(res.is_ok());
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
/// # 8, 114, 85, 4, 25];
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
///```
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
@@ -55,33 +26,6 @@ pub fn encrypt(
Ok(())
}
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
/// `ad`. using ChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
///
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
/// const PLAINTEXT_LEN: usize = 43;
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
///
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
///
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
/// assert!(res.is_ok());
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(expected_plaintext, plaintext_buffer);
///
///```
#[inline]
pub fn decrypt(
plaintext: &mut [u8],

View File

@@ -3,40 +3,10 @@ use rosenpass_to::To;
use zeroize::Zeroize;
/// The key length is 32 bytes or 256 bits.
pub const KEY_LEN: usize = 32; // Grrrr! Libcrux, please provide me these constants.
/// The MAC tag length is 16 bytes or 128 bits.
pub const TAG_LEN: usize = 16;
/// The nonce length is 12 bytes or 96 bits.
pub const NONCE_LEN: usize = 12;
/// Encrypts using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
/// Key and nonce MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN]. The last [TAG_LEN] bytes
/// written in `ciphertext` are the tag guaranteeing integrity. `ciphertext` MUST have a capacity of
/// `plaintext.len()` + [TAG_LEN].
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
///
/// const PLAINTEXT_LEN: usize = 43;
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut ciphertext_buffer = [0u8; PLAINTEXT_LEN + TAG_LEN];
///
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
/// assert!(res.is_ok());
/// # let expected_ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
/// # 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
/// # 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
/// # 8, 114, 85, 4, 25];
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
///```
///
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
@@ -63,33 +33,6 @@ pub fn encrypt(
Ok(())
}
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
/// `ad`. using ChaCha20Poly1305 as implemented in [libcrux](https://github.com/cryspen/libcrux).
///
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN].
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::chacha20poly1305_ietf_libcrux::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
/// let ciphertext: &[u8] = &[239, 104, 148, 202, 120, 32, 77, 27, 246, 206, 226, 17,
/// 83, 78, 122, 116, 187, 123, 70, 199, 58, 130, 21, 1, 107, 230, 58, 77, 18, 152, 31, 159, 80,
/// 151, 72, 27, 236, 137, 60, 55, 180, 31, 71, 97, 199, 12, 60, 155, 70, 221, 225, 110, 132, 191,
/// 8, 114, 85, 4, 25]; // this is the ciphertext generated by the example for the encryption
/// const PLAINTEXT_LEN: usize = 43;
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN, ciphertext.len());
///
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
///
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, nonce, additional_data, ciphertext);
/// assert!(res.is_ok());
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(expected_plaintext, plaintext_buffer);
///
///```
#[inline]
pub fn decrypt(
plaintext: &mut [u8],

View File

@@ -6,15 +6,10 @@ use rosenpass_to::{ops::copy_slice, with_destination, To};
use crate::subtle::blake2b;
/// The key length, 32 bytes or 256 bits.
pub const KEY_LEN: usize = 32;
/// The minimal key length, identical to [KEY_LEN]
pub const KEY_MIN: usize = KEY_LEN;
/// The maximal key length, identical to [KEY_LEN]
pub const KEY_MAX: usize = KEY_LEN;
/// The minimal output length, see [blake2b::OUT_MIN]
pub const OUT_MIN: usize = blake2b::OUT_MIN;
/// The maximal output length, see [blake2b::OUT_MAX]
pub const OUT_MAX: usize = blake2b::OUT_MAX;
/// This is a woefully incorrect implementation of hmac_blake2b.
@@ -24,22 +19,6 @@ pub const OUT_MAX: usize = blake2b::OUT_MAX;
///
/// This will be replaced, likely by Kekkac at some point soon.
/// <https://github.com/rosenpass/rosenpass/pull/145>
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::incorrect_hmac_blake2b::hash;
/// use rosenpass_to::To;
/// let key: [u8; 32] = [0; 32];
/// let data: [u8; 32] = [255; 32];
/// // buffer for the hash output
/// let mut hash_data: [u8; 32] = [0u8; 32];
///
/// assert!(hash(&key, &data).to(&mut hash_data).is_ok(), "Hashing has to return OK result");
/// # let expected_hash: &[u8] = &[5, 152, 135, 141, 151, 106, 147, 8, 220, 95, 38, 66, 29, 33, 3,
/// 104, 250, 114, 131, 119, 27, 56, 59, 44, 11, 67, 230, 113, 112, 20, 80, 103];
/// # assert_eq!(hash_data, expected_hash);
///```
///
#[inline]
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];

View File

@@ -1,9 +1,3 @@
/// 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).
/// - [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;
#[cfg(not(feature = "experiment_libcrux"))]
pub mod chacha20poly1305_ietf;

View File

@@ -6,41 +6,10 @@ use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::XChaCha20Poly1305 as AeadImpl;
use chacha20poly1305::{AeadCore, AeadInPlace, KeyInit, KeySizeUser};
/// The key length is 32 bytes or 256 bits.
pub const KEY_LEN: usize = typenum2const! { <AeadImpl as KeySizeUser>::KeySize };
/// The MAC tag length is 16 bytes or 128 bits.
pub const TAG_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::TagSize };
/// The nonce length is 24 bytes or 192 bits.
pub const NONCE_LEN: usize = typenum2const! { <AeadImpl as AeadCore>::NonceSize };
/// Encrypts using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
/// `key` and `nonce` MUST be chosen (pseudo-)randomly. The `key` slice MUST have a length of
/// [KEY_LEN]. The `nonce` slice MUST have a length of [NONCE_LEN].
/// In contrast to [chacha20poly1305_ietf::encrypt](crate::subtle::chacha20poly1305_ietf::encrypt) and
/// [chacha20poly1305_ietf_libcrux::encrypt](crate::subtle::chacha20poly1305_ietf_libcrux::encrypt),
/// `nonce` is also written into `ciphertext` and therefore ciphertext MUST have a length
/// of at least [NONCE_LEN] + `plaintext.len()` + [TAG_LEN].
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{encrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
/// const PLAINTEXT_LEN: usize = 43;
/// let plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(PLAINTEXT_LEN, plaintext.len());
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut ciphertext_buffer = [0u8; NONCE_LEN + PLAINTEXT_LEN + TAG_LEN];
///
///
/// let res: anyhow::Result<()> = encrypt(&mut ciphertext_buffer, key, nonce, additional_data, plaintext);
/// # assert!(res.is_ok());
/// # let expected_ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
/// # 73, 138, 176, 193, 61, 248, 61, 183, 164, 192, 214, 168, 4, 1, 62, 243, 36, 48, 149, 164, 6];
/// # assert_eq!(expected_ciphertext, &ciphertext_buffer);
///```
#[inline]
pub fn encrypt(
ciphertext: &mut [u8],
@@ -59,38 +28,6 @@ pub fn encrypt(
Ok(())
}
/// Decrypts a `ciphertext` and verifies the integrity of the `ciphertext` and the additional data
/// `ad`. using XChaCha20Poly1305 as implemented in [RustCrypto](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305).
///
/// The `key` slice MUST have a length of [KEY_LEN]. The `nonce` slice MUST have a length of
/// [NONCE_LEN]. The plaintext buffer must have a capacity of `ciphertext.len()` - [TAG_LEN] - [NONCE_LEN].
///
/// In contrast to [chacha20poly1305_ietf::decrypt](crate::subtle::chacha20poly1305_ietf::decrypt) and
/// [chacha20poly1305_ietf_libcrux::decrypt](crate::subtle::chacha20poly1305_ietf_libcrux::decrypt),
/// `ciperhtext` MUST include the as it is not given otherwise.
///
/// # Examples
///```rust
/// # use rosenpass_ciphers::subtle::xchacha20poly1305_ietf::{decrypt, TAG_LEN, KEY_LEN, NONCE_LEN};
/// let ciphertext: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/// # 0, 0, 0, 0, 8, 241, 229, 253, 200, 81, 248, 30, 183, 149, 134, 168, 149, 87, 109, 49, 159, 108,
/// # 206, 89, 51, 232, 232, 197, 163, 253, 254, 208, 73, 76, 253, 13, 247, 162, 133, 184, 177, 44,
/// # 73, 138, 176, 193, 61, 248, 61, 183, 164, 192, 214, 168, 4, 1, 62, 243, 36, 48, 149, 164, 6];
/// // this is the ciphertext generated by the example for the encryption
/// const PLAINTEXT_LEN: usize = 43;
/// assert_eq!(PLAINTEXT_LEN + TAG_LEN + NONCE_LEN, ciphertext.len());
///
/// let key: &[u8] = &[0u8; KEY_LEN]; // THIS IS NOT A SECURE KEY
/// let nonce: &[u8] = &[0u8; NONCE_LEN]; // THIS IS NOT A SECURE NONCE
/// let additional_data: &[u8] = "the encrypted message is very important".as_bytes();
/// let mut plaintext_buffer = [0u8; PLAINTEXT_LEN];
///
/// let res: anyhow::Result<()> = decrypt(&mut plaintext_buffer, key, additional_data, ciphertext);
/// assert!(res.is_ok());
/// let expected_plaintext = "post-quantum cryptography is very important".as_bytes();
/// assert_eq!(expected_plaintext, plaintext_buffer);
///
///```
#[inline]
pub fn decrypt(
plaintext: &mut [u8],

View File

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

View File

@@ -2,29 +2,14 @@
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;
@@ -92,23 +77,3 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 {
assert!(a.len() == b.len());
unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) }
}
#[cfg(test)]
mod tests {
use crate::compare::memcmp_le;
#[test]
fn memcmp_le_test() {
let a = [1, 2, 3, 4];
let b = [1, 2, 3, 4];
let c = [1, 2, 2, 5];
let d = [1, 2, 2, 4];
unsafe {
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0);
assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0);
assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0);
assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0);
}
}
}

View File

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

View File

@@ -7,32 +7,6 @@
//! ## 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;
@@ -40,7 +14,6 @@ mod memcmp;
mod xor;
pub use compare::compare;
pub use compare::memcmp_le;
pub use increment::increment;
pub use memcmp::memcmp;
pub use xor::xor;

View File

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

View File

@@ -5,23 +5,12 @@ 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
/// 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.
/// TODO: mention here if this function leaks any information, see
/// <https://github.com/rosenpass/rosenpass/issues/232>
///
/// ## Tests
/// For discussion on how to ensure the constant-time execution of this function, see

View File

@@ -1,45 +0,0 @@
#! /usr/bin/env bash
set -e -o pipefail
OUTPUT_DIR="target/grcov"
log() {
echo >&2 "$@"
}
exc() {
echo '$' "$@"
"$@"
}
main() {
exc cd "$(dirname "$0")"
local open="0"
if [[ "$1" == "--open" ]]; then
open="1"
fi
exc cargo llvm-cov --all-features --workspace --doctests --branch
exc cp -rv target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/doctestbins
exc rm -rf "${OUTPUT_DIR}"
exc mkdir -p "${OUTPUT_DIR}"
exc grcov target/llvm-cov-target/ --llvm -s . --branch \
--binary-path ./target/llvm-cov-target/debug/deps \
--ignore-not-existing --ignore '../*' --ignore "/*" \
--excl-line '^\s*#\[(derive|repr)\(' \
-t lcov,html,markdown -o "${OUTPUT_DIR}"
if (( "${open}" == 1 )); then
xdg-open "${PWD}/${OUTPUT_DIR}/html/index.html"
fi
log ""
log "Generated reports in \"${PWD}/${OUTPUT_DIR}\"."
log "Open \"${PWD}/${OUTPUT_DIR}/html/index.html\" to view HTML report."
log ""
}
main "$@"

View File

@@ -109,27 +109,11 @@
proverif-patched
];
};
# TODO: Write this as a patched version of the default environment
devShells.fullEnv = pkgs.mkShell {
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = with pkgs; [
cargo-release
rustfmt
nodePackages.prettier
nushell # for the .ci/gen-workflow-files.nu script
proverif-patched
inputs.fenix.packages.${system}.complete.toolchain
pkgs.cargo-llvm-cov
pkgs.grcov
];
};
devShells.coverage = pkgs.mkShell {
inputsFrom = [ pkgs.rosenpass ];
nativeBuildInputs = [
inputs.fenix.packages.${system}.complete.toolchain
pkgs.cargo-llvm-cov
pkgs.grcov
];
};

View File

@@ -2,8 +2,8 @@
template: rosenpass
title: Rosenpass
author:
- Karolin Varner = Rosenpass e.V., Max Planck Institute for Security and Privacy (MPI-SP)
- Benjamin Lipp = Rosenpass e.V., Max Planck Institute for Security and Privacy (MPI-SP)
- Karolin Varner = Independent Researcher
- Benjamin Lipp = Max Planck Institute for Security and Privacy (MPI-SP)
- Wanja Zaeske
- Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}}
- Prabhpreet Dua
@@ -383,18 +383,9 @@ fn load_biscuit(nct) {
"biscuit additional data",
spkr, sidi, sidr);
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
// Find the peer and apply retransmission protection
lookup_peer(pt.peerid);
// In December 2024, the InitConf retransmission mechanisim was redesigned
// in a backwards-compatible way. See the changelog.
//
// -- 2024-11-30, Karolin Varner
if (protocol_version!(< "0.3.0")) {
// Ensure that the biscuit is used only once
assert(pt.biscuit_no <= peer.biscuit_used);
}
assert(pt.biscuit_no <= peer.biscuit_used);
// Restore the chaining key
ck ← pt.ck;
@@ -510,7 +501,7 @@ LAST_UNDER_LOAD_WINDOW = 1 //seconds
The initiator deals with packet loss by storing the messages it sends to the responder and retransmitting them in randomized, exponentially increasing intervals until they get a response. Receiving RespHello terminates retransmission of InitHello. A Data or EmptyData message serves as acknowledgement of receiving InitConf and terminates its retransmission.
The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted.
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
### Interaction with cookie reply system
@@ -524,76 +515,6 @@ When the responder is under load and it recieves an InitConf message, the messag
# Changelog
### 0.3.x
#### 2024-10-30 InitConf retransmission updates
\vspace{0.5em}
Author: Karolin Varner
Issue: [#331](https://github.com/rosenpass/rosenpass/issues/331)
PR: [#513](https://github.com/rosenpass/rosenpass/pull/513)
\vspace{0.5em}
We redesign the InitConf retransmission mechanism to use a hash table. This avoids the need for the InitConf handling code to account for InitConf retransmission specifically and moves the retransmission logic into less-sensitive code.
Previously, we would specifically account for InitConf retransmission in the InitConf handling code by checking the biscuit number: If the biscuit number was higher than any previously seen biscuit number, then this must be a new key-exchange being completed; if the biscuit number was exactly the highest seen biscuit number, then the InitConf message is interpreted as an InitConf retransmission; in this case, an entirely new EmptyData (responder confirmation) message was generated as confirmation that InitConf has been received and that the initiator can now cease opportunistic retransmission of InitConf.
This mechanism was a bit brittle; even leading to a very minor but still relevant security issue, necessitating the release of Rosenpass maintenance version 0.2.2 with a [fix for the problem](https://github.com/rosenpass/rosenpass/pull/329). We had processed the InitConf message, correctly identifying that InitConf was a retransmission, but we failed to pass this information on to the rest of the code base, leading to double emission of the same "hey, we have a new cryptographic session key" even if the `outfile` option was used to integrate Rosenpass into some external application. If this event was used anywhere to reset a nonce, then this could have led to a nonce-misuse, although for the use with WireGuard this is not an issue.
By removing all retransmission handling code from the cryptographic protocol, we are taking structural measures to exclude the possibilities of similar issues.
- In section "Dealing With Package Loss" we replace
\begin{quote}
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
\end{quote}
by
\begin{quote}
The responder uses less complex form of the same mechanism: The responder never retransmits RespHello, instead the responder generates a new RespHello message if InitHello is retransmitted. Responder confirmation messages of completed handshake (EmptyData) messages are retransmitted by storing the most recent InitConf messages (or their hashes) and caching the associated EmptyData messages. Through this cache, InitConf retransmission is detected and the associated EmptyData message is retransmitted.
\end{quote}
- In function `load_biscuit` we replace
``` {=tex}
\begin{quote}
\begin{minted}{pseudorust}
assert(pt.biscuit_no <= peer.biscuit_used);
\end{minted}
\end{quote}
```
by
``` {=tex}
\begin{quote}
\begin{minted}{pseudorust}
// In December 2024, the InitConf retransmission mechanisim was redesigned
// in a backwards-compatible way. See the changelog.
//
// -- 2024-11-30, Karolin Varner
if (protocol_version!(< "0.3.0")) {
// Ensure that the biscuit is used only once
assert(pt.biscuit_no <= peer.biscuit_used);
}
\end{minted}
\end{quote}
```
#### 2024-04-16 Denial of Service Mitigation
\vspace{0.5em}
Author: Prabhpreet Dua
Issue: [#137](https://github.com/rosenpass/rosenpass/issues/137)
PR: [#142](https://github.com/rosenpass/rosenpass/pull/142)
\vspace{0.5em}
- Added denial of service mitigation using the WireGuard cookie mechanism
- Added section "Denial of Service Mitigation and Cookies", and modify "Dealing with Packet Loss" for DoS cookie mechanism
\printbibliography

View File

@@ -23,12 +23,6 @@ rosenpass help
Follow [quick start instructions](https://rosenpass.eu/#start) to get a VPN up and running.
## Contributing
Contributions are generally welcome. Join our [Matrix Chat](https://matrix.to/#/#rosenpass:matrix.org) if you are looking for guidance on how to contribute or for people to collaborate with.
We also have a as of now, very minimal [contributors guide](CONTRIBUTING.md).
## Software architecture
The [rosenpass tool](./src/) is written in Rust and uses liboqs[^liboqs]. The tool establishes a symmetric key and provides it to WireGuard. Since it supplies WireGuard with key through the PSK feature using Rosenpass+WireGuard is cryptographically no less secure than using WireGuard on its own ("hybrid security"). Rosenpass refreshes the symmetric key every two minutes.

View File

@@ -62,7 +62,6 @@ heck = { workspace = true, optional = true }
command-fds = { workspace = true, optional = true }
rustix = { workspace = true, optional = true }
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
signal-hook = { workspace = true, optional = true }
[build-dependencies]
anyhow = { workspace = true }
@@ -88,9 +87,5 @@ experiment_api = [
"rosenpass-util/experiment_file_descriptor_passing",
"rosenpass-wireguard-broker/experiment_api",
]
internal_signal_handling_for_coverage_reports = ["signal-hook"]
internal_testing = []
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage)'] }

View File

@@ -1,5 +1,3 @@
//! The bulk code relating to the Rosenpass unix socket API
mod api_handler;
mod boilerplate;

View File

@@ -167,8 +167,6 @@ const EVENT_CAPACITY: usize = 20;
// TODO add user control via unix domain socket and stdin/stdout
#[derive(Debug)]
pub struct AppServer {
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
pub term_signal: terminate::TerminateRequested,
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events,
@@ -635,8 +633,6 @@ impl AppServer {
};
Ok(Self {
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
term_signal: terminate::TerminateRequested::new()?,
crypto_site,
peers: Vec::new(),
verbosity,
@@ -758,35 +754,18 @@ impl AppServer {
Ok(AppPeerPtr(pn))
}
pub fn event_loop(&mut self) -> anyhow::Result<()> {
pub fn listen_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_without_error_handling() {
let err = match self.event_loop() {
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
@@ -811,7 +790,7 @@ impl AppServer {
}
}
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
pub fn event_loop(&mut self) -> anyhow::Result<()> {
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
/// if socket address for peer is known, call closure
@@ -1309,48 +1288,3 @@ 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)
}
}
}

View File

@@ -3,7 +3,6 @@ use heck::ToShoutySnakeCase;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
/// Recursively calculate a concrete hash value for an API message type
fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> {
match values.split_first() {
Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail),
@@ -11,7 +10,6 @@ fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]
}
}
/// Print a hash literal for pasting into the Rosenpass source code
fn print_literal(path: &[&str]) -> Result<()> {
let val = calculate_hash_value(HashDomain::zero(), path)?;
let (last, prefix) = path.split_last().context("developer error!")?;
@@ -35,8 +33,6 @@ fn print_literal(path: &[&str]) -> Result<()> {
Ok(())
}
/// Tree of domain separators where each leaf represents
/// an API message ID
#[derive(Debug, Clone)]
enum Tree {
Branch(String, Vec<Tree>),
@@ -72,7 +68,6 @@ impl Tree {
}
}
/// Helper for generating hash-based message IDs for the IPC API
fn main() -> Result<()> {
let tree = Tree::Branch(
"Rosenpass IPC API".to_owned(),

View File

@@ -1,68 +1,13 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness.
//!
//! This ensures [domain separation](https://en.wikipedia.org/wiki/Domain_separation) is used
//! across the Rosenpass protocol.
//!
//! There is a chart containing all hash domains used in Rosenpass in the
//! [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository).
//!
//! # Tutorial
//!
//! ```
//! use rosenpass::{hash_domain, hash_domain_ns};
//! use rosenpass::hash_domains::protocol;
//!
//! // Declaring a custom hash domain
//! hash_domain_ns!(protocol, custom_domain, "my custom hash domain label");
//!
//! // Declaring a custom hashers
//! hash_domain_ns!(custom_domain, hashers, "hashers");
//! hash_domain_ns!(hashers, hasher1, "1");
//! hash_domain_ns!(hashers, hasher2, "2");
//!
//! // Declaring specific domain separators
//! hash_domain_ns!(custom_domain, domain_separators, "domain separators");
//! hash_domain!(domain_separators, sep1, "1");
//! hash_domain!(domain_separators, sep2, "2");
//!
//! // Generating values under hasher1 with both domain separators
//! let h1 = hasher1()?.mix(b"some data")?.dup();
//! let h1v1 = h1.mix(&sep1()?)?.mix(b"More data")?.into_value();
//! let h1v2 = h1.mix(&sep2()?)?.mix(b"More data")?.into_value();
//!
//! // Generating values under hasher2 with both domain separators
//! let h2 = hasher2()?.mix(b"some data")?.dup();
//! let h2v1 = h2.mix(&sep1()?)?.mix(b"More data")?.into_value();
//! let h2v2 = h2.mix(&sep2()?)?.mix(b"More data")?.into_value();
//!
//! // All of the domain separators are now different, random strings
//! let values = [h1v1, h1v2, h2v1, h2v2];
//! for i in 0..values.len() {
//! for j in (i+1)..values.len() {
//! assert_ne!(values[i], values[j]);
//! }
//! }
//!
//! Ok::<(), anyhow::Error>(())
//! ```
//! ensures their uniqueness
use anyhow::Result;
use rosenpass_ciphers::hash_domain::HashDomain;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
/// Declare a hash function
///
/// # Examples
///
/// See the source file for details about how this is used concretely.
///
/// See the [module](self) documentation on how to use the hash domains in general
// TODO Use labels that can serve as identifiers
#[macro_export]
macro_rules! hash_domain_ns {
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
$(#[$($attrss)*])*
pub fn $name() -> ::anyhow::Result<::rosenpass_ciphers::hash_domain::HashDomain> {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<HashDomain> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
@@ -70,18 +15,9 @@ macro_rules! hash_domain_ns {
}
}
/// Declare a concrete hash value
///
/// # Examples
///
/// See the source file for details about how this is used concretely.
///
/// See the [module](self) documentation on how to use the hash domains in general
#[macro_export]
macro_rules! hash_domain {
($(#[$($attrss:tt)*])* $base:ident, $name:ident, $($lbl:expr),+ ) => {
$(#[$($attrss)*])*
pub fn $name() -> ::anyhow::Result<[u8; ::rosenpass_ciphers::KEY_LEN]> {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
@@ -89,227 +25,24 @@ macro_rules! hash_domain {
}
}
/// The hash domain containing the protocol string.
///
/// This serves as a global [domain separator](https://en.wikipedia.org/wiki/Domain_separation)
/// used in various places in the rosenpass protocol.
///
/// This is generally used to create further hash-domains for specific purposes. See
///
/// # Examples
///
/// See the source file for details about how this is used concretely.
///
/// See the [module](self) documentation on how to use the hash domains in general
pub fn protocol() -> Result<HashDomain> {
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
hash_domain_ns!(
/// Hash domain based on [protocol] for calculating [crate::msgs::Envelope::mac].
///
/// # Examples
///
/// See the source of [crate::msgs::Envelope::seal] and [crate::msgs::Envelope::check_seal]
/// to figure out how this is concretely used.
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, mac, "mac");
hash_domain_ns!(
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
///
/// # Examples
///
/// See the source of [crate::msgs::Envelope::seal_cookie],
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
/// [crate::protocol::CryptoServer::handle_cookie_reply]
/// to figure out how this is concretely used.
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, cookie, "cookie");
hash_domain_ns!(
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
///
/// # Examples
///
/// See the source of [crate::msgs::Envelope::seal_cookie],
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
/// [crate::protocol::CryptoServer::handle_cookie_reply]
/// to figure out how this is concretely used.
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, cookie_value, "cookie-value");
hash_domain_ns!(
/// Hash domain based on [protocol] involved in calculating [crate::msgs::Envelope::cookie].
///
/// # Examples
///
/// See the source of [crate::msgs::Envelope::seal_cookie],
/// [crate::protocol::CryptoServer::handle_msg_under_load], and
/// [crate::protocol::CryptoServer::handle_cookie_reply]
/// to figure out how this is concretely used.
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, cookie_key, "cookie-key");
hash_domain_ns!(
/// Hash domain based on [protocol] for calculating the peer id as transmitted (encrypted)
/// in [crate::msgs::InitHello::pidic].
///
/// # Examples
///
/// See the source of [crate::protocol::CryptoServer::pidm] and
/// [crate::protocol::Peer::pidt]
/// to figure out how this is concretely used.
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, peerid, "peer id");
hash_domain_ns!(
/// Hash domain based on [protocol] for calculating the additional data
/// during [crate::msgs::Biscuit] encryption, storing the biscuit into
/// [crate::msgs::RespHello::biscuit].
///
/// # Examples
///
/// To understand how the biscuit is used, it is best to read
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
/// [crate::protocol::HandshakeState::load_biscuit]
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, biscuit_ad, "biscuit additional data");
hash_domain_ns!(
/// This hash domain begins our actual handshake procedure, initializing the
/// chaining key [crate::protocol::HandshakeState::ck].
///
/// # Examples
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, ckinit, "chaining key init");
hash_domain_ns!(
/// Namespace for chaining key usage domain separators.
///
/// During the execution of the Rosenpass protocol, we use the chaining key for multiple
/// purposes, so to make sure that we have unique value domains, we mix a domain separator
/// into the chaining key before using it for any particular purpose.
///
/// We could use the full domain separation strings, but using a hash value here is nice
/// because it does not lead to any constraints about domain separator format and we can
/// even allow third parties to define their own separators by claiming a namespace.
///
/// # Examples
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
protocol, _ckextract, "chaining key extract");
hash_domain_ns!(protocol, mac, "mac");
hash_domain_ns!(protocol, cookie, "cookie");
hash_domain_ns!(protocol, cookie_value, "cookie-value");
hash_domain_ns!(protocol, cookie_key, "cookie-key");
hash_domain_ns!(protocol, peerid, "peer id");
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
hash_domain_ns!(protocol, ckinit, "chaining key init");
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
hash_domain!(
/// Used to mix in further values into the chaining key during the handshake.
///
/// See [_ckextract].
///
/// # Examples
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, mix, "mix");
hash_domain!(
/// Chaining key domain separator for generating encryption keys that can
/// encrypt parts of the handshake.
///
/// See [_ckextract].
///
/// # Examples
///
/// Encryption of data during the handshake happens in
/// [crate::protocol::HandshakeState::encrypt_and_mix] and decryption happens in
/// [crate::protocol::HandshakeState::decrypt_and_mix]. See their source code
/// for details.
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, hs_enc, "handshake encryption");
hash_domain!(
/// Chaining key domain separator for live data encryption.
/// Live data encryption is only used to send confirmation of handshake
/// done in [crate::msgs::EmptyData].
///
/// See [_ckextract].
///
/// # Examples
///
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, ini_enc, "initiator handshake encryption");
hash_domain!(
/// Chaining key domain separator for live data encryption.
/// Live data encryption is only used to send confirmation of handshake
/// done in [crate::msgs::EmptyData].
///
/// See [_ckextract].
///
/// # Examples
///
/// This domain separator finds use in [crate::protocol::HandshakeState::enter_live].
/// Check out its source code!
///
/// To understand how the chaining key is used, study
/// [crate::protocol::HandshakeState], especially [crate::protocol::HandshakeState::init]
/// and [crate::protocol::HandshakeState::mix].
///
/// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, res_enc, "responder handshake encryption");
hash_domain!(_ckextract, mix, "mix");
hash_domain!(_ckextract, hs_enc, "handshake encryption");
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
hash_domain_ns!(
/// Chaining key domain separator for any usage specific purposes.
///
/// We do recommend that third parties base their specific domain separators
/// on a internet domain and/or mix in much more specific information.
///
/// We only really use this to derive a output key for wireguard; see [osk].
///
/// See [_ckextract].
///
/// # Examples
///
/// See the [module](self) documentation on how to use the hash domains in general.
_ckextract, _user, "user");
hash_domain_ns!(
/// Chaining key domain separator for any rosenpass specific purposes.
///
/// We only really use this to derive a output key for wireguard; see [osk].
///
/// See [_ckextract].
///
/// # Examples
///
/// See the [module](self) documentation on how to use the hash domains in general.
_user, _rp, "rosenpass.eu");
hash_domain!(
/// Chaining key domain separator for deriving the key sent to WireGuard.
///
/// See [_ckextract].
///
/// # Examples
///
/// This domain separator finds use in [crate::protocol::CryptoServer::osk].
/// Check out its source code!
///
/// See the [module](self) documentation on how to use the hash domains in general.
_rp, osk, "wireguard psk");
hash_domain_ns!(_ckextract, _user, "user");
hash_domain_ns!(_user, _rp, "rosenpass.eu");
hash_domain!(_rp, osk, "wireguard psk");

View File

@@ -1,18 +1,3 @@
//! This is the central rosenpass crate implementing the rosenpass protocol.
//!
//! - [crate::app_server] contains the business logic of rosenpass, handling networking
//! - [crate::cli] contains the cli parsing logic and contains quite a bit of startup logic; the
//! main function quickly hands over to [crate::cli::CliArgs::run] which contains quite a bit
//! of our startup logic
//! - [crate::config] has the code to parse and generate configuration files
//! - [crate::hash_domains] lists the different hash function domains used in the Rosenpass
//! protocol
//! - [crate::msgs] provides declarations of the Rosenpass protocol network messages and facilities
//! to parse those messages through the [::zerocopy] crate
//! - [crate::protocol] this is where the bulk of our code lives; this module contains the actual
//! cryptographic protocol logic
//! - crate::api implements the Rosenpass unix socket API, if feature "experiment_api" is active
#[cfg(feature = "experiment_api")]
pub mod api;
pub mod app_server;
@@ -22,25 +7,14 @@ pub mod hash_domains;
pub mod msgs;
pub mod protocol;
/// Error types used in diverse places across Rosenpass
#[derive(thiserror::Error, Debug)]
pub enum RosenpassError {
/// Usually indicates that parsing a struct through the
/// [::zerocopy] crate failed
#[error("buffer size mismatch")]
BufferSizeMismatch,
/// Mostly raised by the `TryFrom<u8>` implementation for [crate::msgs::MsgType]
/// to indicate that a message type is not defined
#[error("invalid message type")]
InvalidMessageType(
/// The message type that could not be parsed
u8,
),
/// Raised by the `TryFrom<RawMsgType>` (crate::api::RawMsgType) implementation for crate::api::RequestMsgType
/// and crate::api::RequestMsgType to indicate that a message type is not defined
InvalidMessageType(u8),
#[error("invalid API message type")]
InvalidApiMessageType(
/// The message type that could not be parsed
u128,
),
InvalidApiMessageType(u128),
#[error("could not parse API message")]
InvalidApiMessage,
}

View File

@@ -1,5 +1,3 @@
//! For the main function
use clap::CommandFactory;
use clap::Parser;
use clap_mangen::roff::{roman, Roff};
@@ -7,7 +5,6 @@ use log::error;
use rosenpass::cli::CliArgs;
use std::process::exit;
/// Printing custom man sections when generating the man page
fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File) {
let mut roff = Roff::default();
roff.control("SH", [section]);
@@ -16,8 +13,6 @@ fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File)
}
/// Catches errors, prints them through the logger, then exits
///
/// The bulk of the command line logic is handled inside [crate::cli::CliArgs::run].
pub fn main() {
// parse CLI arguments
let args = CliArgs::parse();
@@ -86,27 +81,21 @@ pub fn main() {
}
}
}
/// Custom main page section: Exit Status
static EXIT_STATUS_MAN: &str = r"
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
/// Custom main page section: See also.
static SEE_ALSO_MAN: &str = r"
rp(1), wg(1)
Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt, Rosenpass, https://rosenpass.eu/whitepaper.pdf, 2023.";
/// Custom main page section: Standards.
static STANDARDS_MAN: &str = r"
This tool is the reference implementation of the Rosenpass protocol, as
specified within the whitepaper referenced above.";
/// Custom main page section: Authors.
static AUTHORS_MAN: &str = r"
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
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.";

View File

@@ -9,75 +9,20 @@
//! To achieve this we utilize the zerocopy library.
//!
use std::mem::size_of;
use std::u8;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use super::RosenpassError;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
/// Length of a session ID such as [InitHello::sidi]
pub const SESSION_ID_LEN: usize = 4;
/// Length of a biscuit ID; i.e. size of the value in [Biscuit::biscuit_no]
pub const BISCUIT_ID_LEN: usize = 12;
/// TODO: Unused, remove!
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
/// Size required to fit any message in binary form
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
/// length in bytes of an unencrypted Biscuit (plain text)
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
/// Length in bytes of an encrypted Biscuit (cipher text)
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
/// Size of the field [Envelope::mac]
pub const MSG_SIZE_LEN: usize = 1;
pub const RESERVED_LEN: usize = 3;
pub const MAC_SIZE: usize = 16;
/// Size of the field [Envelope::cookie]
pub const COOKIE_SIZE: usize = MAC_SIZE;
pub const COOKIE_SIZE: usize = 16;
pub const SID_LEN: usize = 4;
/// Type of the mac field in [Envelope]
pub type MsgEnvelopeMac = [u8; MAC_SIZE];
/// Type of the cookie field in [Envelope]
pub type MsgEnvelopeCookie = [u8; COOKIE_SIZE];
/// Header and footer included in all our packages,
/// including a type field.
///
/// # Examples
///
/// ```
/// use rosenpass::msgs::{Envelope, InitHello};
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
/// use memoffset::offset_of;
///
/// // Zero-initialization
/// let mut ih = Envelope::<InitHello>::new_zeroed();
///
/// // Edit fields normally
/// ih.mac[0] = 1;
///
/// // Edit as binary
/// ih.as_bytes_mut()[offset_of!(Envelope<InitHello>, msg_type)] = 23;
/// assert_eq!(ih.msg_type, 23);;
///
/// // Conversion to bytes
/// let mut ih2 = ih.as_bytes().to_owned();
///
/// // Setting msg_type field, again
/// ih2[0] = 42;
///
/// // Zerocopy parsing
/// let ih3 = Ref::<&mut [u8], Envelope<InitHello>>::new(&mut ih2).unwrap();
/// assert_ne!(ih.as_bytes(), ih3.as_bytes());
/// assert_eq!(ih3.msg_type, 42);
/// ```
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Envelope<M: AsBytes + FromBytes> {
/// [MsgType] of this message
pub msg_type: u8,
@@ -87,45 +32,11 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M,
/// Message Authentication Code (mac) over all bytes until (exclusive)
/// `mac` itself
pub mac: MsgEnvelopeMac,
pub mac: [u8; 16],
/// Currently unused, TODO: do something with this
pub cookie: MsgEnvelopeCookie,
pub cookie: [u8; 16],
}
/// This is the first message sent by the initiator to the responder
/// during the execution of the Rosenpass protocol.
///
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
///
/// # Examples
///
/// Check out the code of [crate::protocol::CryptoServer::handle_initiation] (generation on
/// iniatiator side) and [crate::protocol::CryptoServer::handle_init_hello] (processing on
/// responder side) to understand how this is used.
///
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
///
/// ```
/// use rosenpass::msgs::{Envelope, InitHello};
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
/// use memoffset::span_of;
///
/// // Zero initialization
/// let mut ih = Envelope::<InitHello>::new_zeroed();
///
/// // Conversion to byte representation
/// let ih = ih.as_bytes_mut();
///
/// // Set value on byte representation
/// ih[span_of!(Envelope<InitHello>, payload)][span_of!(InitHello, sidi)]
/// .copy_from_slice(&[1,2,3,4]);
///
/// // Conversion from bytes
/// let ih = Ref::<&mut [u8], Envelope<InitHello>>::new(ih).unwrap();
///
/// // Check that write above on byte representation was effective
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
/// ```
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct InitHello {
@@ -141,40 +52,6 @@ pub struct InitHello {
pub auth: [u8; aead::TAG_LEN],
}
/// This is the second message sent by the responder to the initiator
/// during the execution of the Rosenpass protocol in response to [InitHello].
///
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
///
/// # Examples
///
/// Check out the code of [crate::protocol::CryptoServer::handle_init_hello] (generation on
/// responder side) and [crate::protocol::CryptoServer::handle_resp_hello] (processing on
/// initiator side) to understand how this is used.
///
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
///
/// ```
/// use rosenpass::msgs::{Envelope, RespHello};
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
/// use memoffset::span_of;
///
/// // Zero initialization
/// let mut ih = Envelope::<RespHello>::new_zeroed();
///
/// // Conversion to byte representation
/// let ih = ih.as_bytes_mut();
///
/// // Set value on byte representation
/// ih[span_of!(Envelope<RespHello>, payload)][span_of!(RespHello, sidi)]
/// .copy_from_slice(&[1,2,3,4]);
///
/// // Conversion from bytes
/// let ih = Ref::<&mut [u8], Envelope<RespHello>>::new(ih).unwrap();
///
/// // Check that write above on byte representation was effective
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
/// ```
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct RespHello {
@@ -192,42 +69,8 @@ pub struct RespHello {
pub biscuit: [u8; BISCUIT_CT_LEN],
}
/// This is the third message sent by the initiator to the responder
/// during the execution of the Rosenpass protocol in response to [RespHello].
///
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
///
/// # Examples
///
/// Check out the code of [crate::protocol::CryptoServer::handle_resp_hello] (generation on
/// initiator side) and [crate::protocol::CryptoServer::handle_init_conf] (processing on
/// responder side) to understand how this is used.
///
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
///
/// ```
/// use rosenpass::msgs::{Envelope, InitConf};
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
/// use memoffset::span_of;
///
/// // Zero initialization
/// let mut ih = Envelope::<InitConf>::new_zeroed();
///
/// // Conversion to byte representation
/// let ih = ih.as_bytes_mut();
///
/// // Set value on byte representation
/// ih[span_of!(Envelope<InitConf>, payload)][span_of!(InitConf, sidi)]
/// .copy_from_slice(&[1,2,3,4]);
///
/// // Conversion from bytes
/// let ih = Ref::<&mut [u8], Envelope<InitConf>>::new(ih).unwrap();
///
/// // Check that write above on byte representation was effective
/// assert_eq!(ih.payload.sidi, [1,2,3,4]);
/// ```
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct InitConf {
/// Copied from InitHello
pub sidi: [u8; 4],
@@ -239,53 +82,8 @@ pub struct InitConf {
pub auth: [u8; aead::TAG_LEN],
}
/// This is the fourth message sent by the initiator to the responder
/// during the execution of the Rosenpass protocol in response to [RespHello].
///
/// When transmitted on the wire, this type will generally be wrapped into [Envelope].
///
/// This message does not serve a cryptographic purpose; it just tells the initiator
/// to stop package retransmission.
///
/// This message should really be called `RespConf`, but when we wrote the protocol,
/// we initially designed the protocol we still though Rosenpass itself should do
/// payload transmission at some point so `EmptyData` could have served as a more generic
/// mechanism.
///
/// We might add payload transmission in the future again, but we will treat
/// it as a protocol extension if we do.
///
/// # Examples
///
/// Check out the code of [crate::protocol::CryptoServer::handle_init_conf] (generation on
/// responder side) and [crate::protocol::CryptoServer::handle_resp_conf] (processing on
/// initiator side) to understand how this is used.
///
/// [Envelope] contains some extra examples on how to use structures from the [::zerocopy] crate.
///
/// ```
/// use rosenpass::msgs::{Envelope, EmptyData};
/// use zerocopy::{AsBytes, FromBytes, Ref, FromZeroes};
/// use memoffset::span_of;
///
/// // Zero initialization
/// let mut ih = Envelope::<EmptyData>::new_zeroed();
///
/// // Conversion to byte representation
/// let ih = ih.as_bytes_mut();
///
/// // Set value on byte representation
/// ih[span_of!(Envelope<EmptyData>, payload)][span_of!(EmptyData, sid)]
/// .copy_from_slice(&[1,2,3,4]);
///
/// // Conversion from bytes
/// let ih = Ref::<&mut [u8], Envelope<EmptyData>>::new(ih).unwrap();
///
/// // Check that write above on byte representation was effective
/// assert_eq!(ih.payload.sid, [1,2,3,4]);
/// ```
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Copy)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct EmptyData {
/// Copied from RespHello
pub sid: [u8; 4],
@@ -295,22 +93,6 @@ pub struct EmptyData {
pub auth: [u8; aead::TAG_LEN],
}
/// Cookie encrypted and sent to the initiator by the responder in [RespHello]
/// and returned by the initiator in [InitConf].
///
/// The encryption key is randomly chosen by the responder and frequently regenerated.
/// Using this biscuit value in the protocol allows us to make sure that the responder
/// is mostly stateless until full initiator authentication is achieved, which is needed
/// to prevent denial of service attacks. See the [whitepaper](https://rosenpass.eu/whitepaper.pdf)
/// ([/papers/whitepaper.md] in this repository).
///
/// # Examples
///
/// To understand how the biscuit is used, it is best to read
/// the code of [crate::protocol::HandshakeState::store_biscuit] and
/// [crate::protocol::HandshakeState::load_biscuit]
///
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Biscuit {
@@ -322,20 +104,12 @@ pub struct Biscuit {
pub ck: [u8; KEY_LEN],
}
/// Specialized message for use in the cookie mechanism.
///
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
///
/// Generally used together with [CookieReply] which brings this up to the size
/// of [InitHello] to avoid amplification Denial of Service attacks.
///
/// # Examples
///
/// To understand how the biscuit is used, it is best to read
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
/// [crate::protocol::CryptoServer::handle_msg_under_load].
///
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct DataMsg {
pub dummy: [u8; 4],
}
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct CookieReplyInner {
@@ -349,20 +123,6 @@ pub struct CookieReplyInner {
pub cookie_encrypted: [u8; xaead::NONCE_LEN + COOKIE_SIZE + xaead::TAG_LEN],
}
/// Specialized message for use in the cookie mechanism.
///
/// This just brings [CookieReplyInner] up to the size
/// of [InitHello] to avoid amplification Denial of Service attacks.
///
/// See the [whitepaper](https://rosenpass.eu/whitepaper.pdf) ([/papers/whitepaper.md] in this repository) for details.
///
/// # Examples
///
/// To understand how the biscuit is used, it is best to read
/// the code of [crate::protocol::CryptoServer::handle_cookie_reply] and
/// [crate::protocol::CryptoServer::handle_msg_under_load].
///
/// [Envelope] and [InitHello] contain some extra examples on how to use structures from the [::zerocopy] crate.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct CookieReply {
@@ -370,46 +130,33 @@ pub struct CookieReply {
pub padding: [u8; size_of::<Envelope<InitHello>>() - size_of::<CookieReplyInner>()],
}
// Traits /////////////////////////////////////////////////////////////////////
pub trait WireMsg: std::fmt::Debug {
const MSG_TYPE: MsgType;
const MSG_TYPE_U8: u8 = Self::MSG_TYPE as u8;
const BYTES: usize;
}
// Constants //////////////////////////////////////////////////////////////////
pub const SESSION_ID_LEN: usize = 4;
pub const BISCUIT_ID_LEN: usize = 12;
pub const WIRE_ENVELOPE_LEN: usize = 1 + 3 + 16 + 16; // TODO verify this
/// Size required to fit any message in binary form
pub const MAX_MESSAGE_LEN: usize = 2500; // TODO fix this
/// Recognized message types
///
/// # Examples
///
/// ```
/// use rosenpass::msgs::MsgType;
/// use rosenpass::msgs::MsgType as M;
///
/// let values = [M::InitHello, M::RespHello, M::InitConf, M::EmptyData, M::CookieReply];
/// let values_u8 = values.map(|v| -> u8 { v.into() });
///
/// // Can be converted to and from u8 using [::std::convert::Into] or [::std::convert::From]
/// for v in values.iter().copied() {
/// let v_u8 : u8 = v.into();
/// let v2 : MsgType = v_u8.try_into()?;
/// assert_eq!(v, v2);
/// }
///
/// // Converting an unsupported type produces an error
/// let invalid_values = (u8::MIN..=u8::MAX)
/// .filter(|v| !values_u8.contains(v));
/// for v in invalid_values {
/// let res : Result<MsgType, _> = v.try_into();
/// assert!(res.is_err());
/// }
///
/// Ok::<(), anyhow::Error>(())
/// ```
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum MsgType {
/// MsgType for [InitHello]
InitHello = 0x81,
/// MsgType for [RespHello]
RespHello = 0x82,
/// MsgType for [InitConf]
InitConf = 0x83,
/// MsgType for [EmptyData]
EmptyData = 0x84,
/// MsgType for [CookieReply]
DataMsg = 0x85,
CookieReply = 0x86,
}
@@ -422,6 +169,7 @@ impl TryFrom<u8> for MsgType {
0x82 => MsgType::RespHello,
0x83 => MsgType::InitConf,
0x84 => MsgType::EmptyData,
0x85 => MsgType::DataMsg,
0x86 => MsgType::CookieReply,
_ => return Err(RosenpassError::InvalidMessageType(value)),
})
@@ -434,6 +182,12 @@ impl From<MsgType> for u8 {
}
}
/// length in bytes of an unencrypted Biscuit (plain text)
pub const BISCUIT_PT_LEN: usize = size_of::<Biscuit>();
/// Length in bytes of an encrypted Biscuit (cipher text)
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
#[cfg(test)]
mod test_constants {
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};

View File

@@ -1,79 +1,3 @@
//! Module containing the cryptographic protocol implementation
//!
//! # Overview
//!
//! The most important types in this module probably are [PollResult]
//! & [CryptoServer]. Once a [CryptoServer] is created, the server is
//! provided with new messages via the [CryptoServer::handle_msg] method.
//! The [CryptoServer::poll] method can be used to let the server work, which
//! will eventually yield a [PollResult]. Said [PollResult] contains
//! prescriptive activities to be carried out. [CryptoServer::osk] can than
//! be used to extract the shared key for two peers, once a key-exchange was
//! successful.
//!
//! TODO explain briefly the role of epki
//!
//! # Example Handshake
//!
//! This example illustrates a minimal setup for a key-exchange between two
//! [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;
//! use rosenpass_secret_memory::policy::*;
//! use rosenpass_cipher_traits::Kem;
//! use rosenpass_ciphers::kem::StaticKem;
//! use rosenpass::{
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
//! };
//! # fn main() -> anyhow::Result<()> {
//! // Set security policy for storing secrets
//!
//! secret_policy_try_use_memfd_secrets();
//!
//! // initialize secret and public key for peer a ...
//! 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())?;
//!
//! // ... and for peer b
//! 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())?;
//!
//! // initialize server and a pre-shared key
//! let psk = SymKey::random();
//! let mut a = CryptoServer::new(peer_a_sk, peer_a_pk.clone());
//! let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone());
//!
//! // introduce peers to each other
//! a.add_peer(Some(psk.clone()), peer_b_pk)?;
//! b.add_peer(Some(psk), peer_a_pk)?;
//!
//! // declare buffers for message exchange
//! let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero());
//!
//! // let a initiate a handshake
//! let mut maybe_len = Some(a.initiate_handshake(PeerPtr(0), a_buf.as_mut_slice())?);
//!
//! // let a and b communicate
//! while let Some(len) = maybe_len {
//! maybe_len = b.handle_msg(&a_buf[..len], &mut b_buf[..])?.resp;
//! std::mem::swap(&mut a, &mut b);
//! std::mem::swap(&mut a_buf, &mut b_buf);
//! }
//!
//! // all done! Extract the shared keys and ensure they are identical
//! let a_key = a.osk(PeerPtr(0))?;
//! let b_key = b.osk(PeerPtr(0))?;
//! assert_eq!(a_key.secret(), b_key.secret(),
//! "the key exchanged failed to establish a shared secret");
//! # Ok(())
//! # }
//! ```
mod build_crypto_server;
#[allow(clippy::module_inception)]
mod protocol;

File diff suppressed because it is too large Load Diff

View File

@@ -33,17 +33,8 @@ struct KillChild(std::process::Child);
impl Drop for KillChild {
fn drop(&mut self) {
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;
}
}
self.0.kill().discard_result();
self.0.wait().discard_result()
}
}
@@ -162,6 +153,7 @@ fn api_integration_api_setup() -> anyhow::Result<()> {
peer_b.config_file_path.to_str().context("")?,
])
.stdin(Stdio::null())
.stderr(Stdio::null())
.stdout(Stdio::piped())
.spawn()?,
);

View File

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

View File

@@ -1,99 +0,0 @@
use rosenpass_util::functional::ApplyExt;
fn expect_section(manpage: &str, section: &str) -> anyhow::Result<()> {
anyhow::ensure!(manpage.lines().any(|line| { line.starts_with(section) }));
Ok(())
}
fn expect_sections(manpage: &str, sections: &[&str]) -> anyhow::Result<()> {
for section in sections.iter().copied() {
expect_section(manpage, section)?;
}
Ok(())
}
fn expect_contents(manpage: &str, patterns: &[&str]) -> anyhow::Result<()> {
for pat in patterns.iter().copied() {
anyhow::ensure!(manpage.contains(pat))
}
Ok(())
}
fn filter_backspace(str: &str) -> anyhow::Result<String> {
let mut out = String::new();
for chr in str.chars() {
if chr == '\x08' {
anyhow::ensure!(out.pop().is_some());
} else {
out.push(chr);
}
}
Ok(out)
}
/// Spot tests about man page generation; these are by far not exhaustive.
#[test]
fn main_fn_generates_manpages() -> anyhow::Result<()> {
let dir = tempfile::TempDir::with_prefix("rosenpass-test-main-fn-generates-mangapges")?;
let cmd_out = test_bin::get_test_bin("rosenpass")
.args(["--generate-manpage", dir.path().to_str().unwrap()])
.output()?;
assert!(cmd_out.status.success());
let expected_manpages = [
"rosenpass.1",
"rosenpass-exchange.1",
"rosenpass-exchange-config.1",
"rosenpass-gen-config.1",
"rosenpass-gen-keys.1",
"rosenpass-keygen.1",
"rosenpass-validate.1",
];
let man_texts: std::collections::HashMap<&str, String> = expected_manpages
.iter()
.copied()
.map(|name| (name, dir.path().join(name)))
.map(|(name, path)| {
let res = std::process::Command::new("man").arg(path).output()?;
assert!(res.status.success());
let body = res
.stdout
.apply(String::from_utf8)?
.apply(|s| filter_backspace(&s))?;
Ok((name, body))
})
.collect::<anyhow::Result<_>>()?;
for (name, body) in man_texts.iter() {
expect_sections(body, &["NAME", "SYNOPSIS", "OPTIONS"])?;
if *name != "rosenpass.1" {
expect_section(body, "DESCRIPTION")?;
}
}
{
let body = man_texts.get("rosenpass.1").unwrap();
expect_sections(
body,
&["EXIT STATUS", "SEE ALSO", "STANDARDS", "AUTHORS", "BUGS"],
)?;
expect_contents(
body,
&[
"[--log-level]",
"rosenpass-exchange-config(1)",
"Start Rosenpass key exchanges based on a configuration file",
"https://rosenpass.eu/whitepaper.pdf",
],
)?;
}
{
let body = man_texts.get("rosenpass-exchange.1").unwrap();
expect_contents(body, &["[-c|--config-file]", "PSK := preshared-key"])?;
}
Ok(())
}

View File

@@ -1,10 +0,0 @@
#[test]
fn main_fn_prints_errors() -> anyhow::Result<()> {
let out = test_bin::get_test_bin("rosenpass")
.args(["exchange-config", "/"])
.output()?;
assert!(!out.status.success());
assert!(String::from_utf8(out.stderr)?.contains("Is a directory (os error 21)"));
Ok(())
}

View File

@@ -1,642 +0,0 @@
/// 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)
}
}

View File

@@ -26,11 +26,10 @@ rosenpass-wireguard-broker = { workspace = true }
tokio = { workspace = true }
futures = "0.3"
futures-util = "0.3"
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
ctrlc-async = "3.2"
futures = "0.3"
futures-util = "0.3"
genetlink = "0.2"
rtnetlink = "0.14"
netlink-packet-core = "0.7"

View File

@@ -3,11 +3,6 @@ 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,
@@ -23,10 +18,6 @@ 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,
@@ -34,24 +25,12 @@ 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 {
@@ -65,9 +44,6 @@ 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();
@@ -150,9 +126,6 @@ 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();
@@ -180,7 +153,7 @@ impl ExchangeOptions {
if let Some(ip) = args.next() {
options.ip = Some(ip);
} else {
return fatal("ip option requires parameter", Some(CommandType::Exchange));
return fatal("is option requires parameter", Some(CommandType::Exchange));
}
}
"listen" => {
@@ -218,9 +191,6 @@ 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();

View File

@@ -11,37 +11,21 @@ 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>,
}
@@ -64,11 +48,8 @@ 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, equivalent to `ip link add <link_name> type wireguard`.
// add the link
rtnetlink
.link()
.add()
@@ -76,8 +57,7 @@ mod netlink {
.execute()
.await?;
// 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`.
// retrieve the link to be able to up it
let link = rtnetlink
.link()
.get()
@@ -89,7 +69,7 @@ mod netlink {
.0
.unwrap()?;
// Up the link, equivalent to `ip link set dev <DEV> up`.
// up the link
rtnetlink
.link()
.set(link.header.index)
@@ -100,16 +80,12 @@ 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);
@@ -135,7 +111,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
@@ -144,12 +120,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?;
@@ -162,9 +138,6 @@ 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(
@@ -173,27 +146,19 @@ 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;
@@ -217,8 +182,6 @@ 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();
@@ -235,7 +198,6 @@ 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")
@@ -261,7 +223,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);
@@ -282,7 +244,6 @@ 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");
@@ -310,7 +271,6 @@ 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");
@@ -358,8 +318,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
peer.endpoint.map(|x| x.to_string()),
)?;
// Configure routes, equivalent to `ip route replace <allowed_ips> dev <dev>` and set up
// the cleanup as `ip route del <allowed_ips>`.
// Configure routes
if let Some(allowed_ips) = peer.allowed_ips {
Command::new("ip")
.arg("route")
@@ -390,8 +349,7 @@ 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 {

View File

@@ -14,7 +14,6 @@ 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"))]
@@ -25,14 +24,6 @@ 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() {
@@ -79,11 +70,6 @@ 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));
@@ -104,11 +90,9 @@ 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(())

View File

@@ -39,7 +39,7 @@ let
}];
};
client_config = {
listen = [ ];
listen = [ "0.0.0.0:9999" ]; # TODO: Should not be necessary to set, but wouldn't parse.
public_key = "/etc/rosenpass/rp0/pqpk";
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
verbosity = "Verbose";

View File

@@ -17,14 +17,13 @@ use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait.
// Unfortunately dealing with lifetimes is a bit more finicky than it would
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
// be without destination parameters
fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest
// way to create them is to use the provided helper functions like with_destination.
with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) {

View File

@@ -1,5 +1,4 @@
//! 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.
//! Functions with destination copying data between slices and arrays.
use crate::{with_destination, To};
@@ -9,17 +8,6 @@ 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,
@@ -35,19 +23,6 @@ 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,
@@ -59,18 +34,6 @@ 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,
@@ -84,24 +47,6 @@ 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,
@@ -117,26 +62,6 @@ 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,
@@ -147,18 +72,6 @@ where
}
/// Function with destination that copies all data between two array references.
///
/// # Example
/// ```rust
/// use rosenpass_to::ops::copy_array;
/// use rosenpass_to::To;
/// let my_arr: [u8; 32] = [0; 32];
/// let to_function = copy_array(&my_arr);
/// let mut dst = [255; 32];
/// to_function.to(&mut dst);
/// // After the operation `dst` will hold the same data as the original slice.
/// assert!(dst.iter().all(|b| *b == 0));
/// ```
pub fn copy_array<T, const N: usize>(origin: &[T; N]) -> impl To<[T; N], ()> + '_
where
T: Copy,

View File

@@ -1,11 +1,6 @@
//! 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 destination from a function with destinations.
/// See the respective functions for usage examples.
/// Named tuple holding the return value and the output from a function with destinations.
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
pub struct Beside<Val, Ret>(pub Val, pub Ret);
@@ -64,7 +59,7 @@ impl<Val, Ret> Beside<Val, Ret> {
&mut self.1
}
/// Perform beside condensation. See [CondenseBeside] for more details.
/// Perform beside condensation. See [CondenseBeside]
///
/// # Example
/// ```
@@ -95,25 +90,3 @@ impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
(val, ret)
}
}
#[cfg(test)]
mod tests {
use crate::Beside;
#[test]
fn from_tuple() {
let tuple = (21u8, 42u16);
let beside: Beside<u8, u16> = Beside::from(tuple);
assert_eq!(beside.dest(), &21u8);
assert_eq!(beside.ret(), &42u16);
}
#[test]
fn from_beside() {
let beside: Beside<u8, u16> = Beside(21u8, 42u16);
type U8u16 = (u8, u16);
let tuple = U8u16::from(beside);
assert_eq!(tuple.0, 21u8);
assert_eq!(tuple.1, 42u16);
}
}

View File

@@ -1,11 +1,4 @@
//! 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`.
/// Beside condensation.
///
/// 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
@@ -13,19 +6,6 @@
///
/// 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;
@@ -37,7 +17,6 @@ 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
}
@@ -46,7 +25,6 @@ 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)
}
@@ -55,7 +33,6 @@ impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
impl<Val> CondenseBeside<Val> for Option<()> {
type Condensed = Option<Val>;
/// Replaces the empty tuple inside this [Option] with `ret`.
fn condense(self, ret: Val) -> Option<Val> {
self.map(|()| ret)
}

View File

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

View File

@@ -6,16 +6,11 @@
//! - `Dst: ?Sized`; (e.g. [u8]) The target to write to
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) A reference to the target to write to
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) Some value that
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or
//! some sized variant of
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
//! `Dst` (e.g. `[u8; 64]`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The
//! ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as
//! `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>)
//! The combiation of Val and Ret after condensing was applied
//! (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
//! - `Ret: Sized`; (anything) must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) Some owned storage that can be borrowed as `Dst`
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
pub mod beside;
pub mod condense;

View File

@@ -1,23 +1,9 @@
//! This module provides the [To::to] function which allows to use functions with destination in
//! a manner akin to that of a variable assignment. See [To::to] for more details.
use crate::{DstCoercion, To};
/// Alias for [To::to] moving the destination to the left.
///
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
/// the variable to assign to on the left and the generating function on the right.
///
/// # Example
/// ```rust
/// // Using the to function to have data flowing from the right to the left,
/// // performing something akin to a variable assignment.
/// use rosenpass_to::ops::copy_slice_least;
/// # use rosenpass_to::to;
/// let mut dst = b" ".to_vec();
/// to(&mut dst[..], copy_slice_least(b"Hello World"));
/// assert_eq!(&dst[..], b"Hello World");
/// ```
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
where
Coercable: ?Sized + DstCoercion<Dst>,

View File

@@ -1,46 +1,12 @@
//! 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| ...)](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());
/// ```
/// [with_destination(|param: &mut Type| ...)] higher order function.
pub trait To<Dst: ?Sized, Ret>: Sized {
/// Writes self to the destination `out` and returns a value of type `Ret`.
///
@@ -53,36 +19,6 @@ 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>,
@@ -95,21 +31,10 @@ 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,
@@ -128,19 +53,6 @@ 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>,
@@ -152,36 +64,6 @@ 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>,
@@ -195,18 +77,6 @@ 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,
@@ -219,19 +89,6 @@ pub trait To<Dst: ?Sized, Ret>: Sized {
/// return value into one.
///
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
///
/// # Example
/// Below, we provide a simple example for the usage of [collect](To::collect).
/// We refer to the overall [crate documentation](crate) for more examples and general
/// explanations.
/// ```
/// use rosenpass_to::Beside;
/// use rosenpass_to::To;
/// use rosenpass_to::ops::*;
///
/// let dst = copy_slice(&[42u8; 16]).collect_beside::<[u8; 16]>().condense();
/// assert_eq!(dst, [42u8; 16]);
/// ```
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
where
Val: Default + BorrowMut<Dst>,

View File

@@ -1,19 +1,15 @@
//! 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`. It is only internally used to implement [with_destination].
/// and return `Ret`.
///
/// # 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,
@@ -21,11 +17,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>
@@ -37,7 +33,6 @@ 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)
}
@@ -53,24 +48,7 @@ where
/// * `Ret` - The return type of the closure
/// * `Fun` - The closure type that implements `FnOnce(&mut Dst) -> Ret`
///
/// 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);
/// }
///
/// ```
/// See the tutorial in [readme.me]..
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
where
Dst: ?Sized,

View File

@@ -78,8 +78,8 @@ pub trait Build<T>: Sized {
/// A type that can be incrementally built from a type that can [Build] it
///
/// This is similar to an option, where [Self::Void] is [std::option::Option::None],
/// [Self::Product] is [std::option::Option::Some], except that there is a third
/// 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
/// 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 in the [`Builder`] phase.
/// Returns `true` if the construction site is [`InProgress`].
///
/// [`Builder`]: ConstructionSite::Builder
/// [`InProgress`]: ConstructionSite::InProgress
///
/// # Examples
///
@@ -541,10 +541,9 @@ where
matches!(self, Self::Builder(..))
}
/// Returns `true` if the construction site is in the [`Product`] phase and
/// is therefore done.
/// Returns `true` if the construction site is [`Done`].
///
/// [`Product`]: ConstructionSite::Product
/// [`Done`]: ConstructionSite::Done
///
/// # Examples
///

View File

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

View File

@@ -89,30 +89,6 @@ 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
@@ -310,14 +286,14 @@ where
}
}
/// Distinguish different socket address families; e.g. IP and unix sockets
/// Distinguish different socket address familys; 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 [Self::socket_domain]
/// Alias for [socket_domain]
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
self.socket_domain()
}
@@ -344,67 +320,9 @@ where
pub trait GetUnixSocketType {
/// Error type returned by operations in this trait
type Error;
/// 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)));
/// ```
/// Check if the socket is a unix stream socket
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<()>;
}
@@ -434,65 +352,16 @@ where
#[cfg(target_os = "linux")]
/// Distinguish between different network socket protocols (e.g. tcp, udp)
pub trait GetSocketProtocol {
/// 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>(())
/// ```
/// Retrieve the socket protocol
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()
}
/// 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>(())
/// ```
/// Return Ok(()) only if the socket is a udp socket
fn demand_udp_socket(&self) -> anyhow::Result<()> {
match self.socket_protocol() {
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),

View File

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

View File

@@ -1,16 +1,3 @@
//! 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;
@@ -21,79 +8,54 @@ use crate::{
result::ensure_or,
};
/// 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.
/// Size in bytes of a message header carrying length information
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.
///
/// # 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)");
/// ```
/// Creates a new MessageTooLargeError with the given message and buffer sizes
pub fn new(msg_size: usize, buf_size: usize) -> Self {
Self { msg_size, buf_size }
}
/// Ensures the message fits within the given buffer.
/// Ensures that the message size fits within the buffer size
///
/// 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)");
/// ```
/// Returns Ok(()) if the message fits, otherwise returns an error with size details
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.
/// Number of bytes read from the input
pub bytes_read: usize,
/// The complete message slice if fully read, otherwise `None`.
/// Optional slice containing the complete message, if one was read
pub message: Option<&'a mut [u8]>,
}
impl<'a> ReadFromIoReturn<'a> {
/// Creates a new `ReadFromIoReturn`.
///
/// Generally used internally to represent partial or complete read results.
/// Creates a new ReadFromIoReturn with the given number of bytes read and optional message slice.
pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self {
Self {
bytes_read,
@@ -102,17 +64,13 @@ 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 reading from the underlying I/O stream.
/// Error occurred while reading from the underlying I/O stream
#[error("Error reading from the underlying stream")]
IoError(#[from] io::Error),
/// The message size exceeded the capacity of the available buffer.
/// Error occurred because message size exceeded buffer capacity
#[error("Message size out of buffer bounds")]
MessageTooLargeError(#[from] MessageTooLargeError),
}
@@ -126,31 +84,11 @@ impl TryIoErrorKind for ReadFromIoError {
}
}
/// A decoder for length-prefixed messages.
///
/// 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)]
/// 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.
pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
header: [u8; HEADER_SIZE],
buf: Buf,
@@ -158,102 +96,33 @@ pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
}
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
/// 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);
/// ```
/// Creates a new LengthPrefixDecoder with the given buffer
pub fn new(buf: Buf) -> Self {
let header = Default::default();
let off = 0;
Self { header, buf, off }
}
/// Clears and zeroes all internal state.
///
/// This zeroizes the header and the buffer, as well as resets the offset to zero.
/// Clears and zeroes all internal state
pub fn clear(&mut self) {
self.zeroize()
}
/// Creates a decoder from parts.
///
/// Typically used for low-level reconstruction of a decoder state.
/// Creates a new LengthPrefixDecoder from its component parts
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
Self { header, buf, off }
}
/// Consumes the decoder and returns its internal parts.
///
/// Returns the header, the underlying buffer, and the current offset.
/// Consumes the decoder and returns its component parts
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.
/// Reads a complete message from the given reader into the decoder.
///
/// 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(_)));
/// ```
/// Retries on interrupts and returns the decoded message buffer on success.
/// Returns an error if the read fails or encounters an unexpected EOF.
pub fn read_all_from_stdio<R: io::Read>(
&mut self,
mut r: R,
@@ -284,19 +153,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
}
}
/// 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");
/// ```
/// Reads from the given reader into the decoder's internal buffers
pub fn read_from_stdio<R: io::Read>(
&mut self,
mut r: R,
@@ -322,12 +179,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
})
}
/// 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)`.
/// Gets the next buffer slice that can be written to
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() {
@@ -350,9 +202,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(None)
}
/// Advances the internal offset by `count` bytes.
///
/// This checks that the offset does not exceed buffer or message limits.
/// Advances the internal offset by the specified number of bytes
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
let off = self.off + count;
let msg_off = off.saturating_sub(HEADER_SIZE);
@@ -370,9 +220,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(())
}
/// 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.
/// Ensures that the internal message buffer is large enough for the message size in the header
pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> {
let buf_size = self.message_buffer().len();
let msg_size = match self.get_header() {
@@ -382,64 +230,53 @@ 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 underlying message buffer.
/// Returns a reference to the message buffer
pub fn message_buffer(&self) -> &[u8] {
self.buf.borrow()
}
/// Returns a mutable reference to the underlying message buffer.
/// Returns a mutable reference to the message buffer
pub fn message_buffer_mut(&mut self) -> &mut [u8] {
self.buf.borrow_mut()
}
/// Returns a reference to the total number of bytes read so far.
/// Returns the number of bytes read so far
pub fn bytes_read(&self) -> &usize {
&self.off
}
/// 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]);
/// ```
/// Consumes the decoder and returns just the message buffer
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 the header has been fully read.
/// Returns whether a complete header has been read
pub fn has_header(&self) -> bool {
self.header_buffer_offset() == HEADER_SIZE
}
/// Returns `true` if the entire message has been read, `false` otherwise.
/// Returns whether a complete message has been read
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?;
let msg_size = match self.get_header() {
@@ -449,55 +286,55 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
Ok(self.message_buffer_avail().len() == msg_size)
}
/// Returns the currently read portion of the header.
/// Returns a slice of the available data in the header buffer
pub fn header_buffer_avail(&self) -> &[u8] {
let off = self.header_buffer_offset();
&self.header_buffer()[..off]
}
/// Returns a mutable slice of the currently read portion of the header.
/// Returns a mutable slice of the available data in the header buffer
pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[..off]
}
/// Returns the remaining unread portion of the header.
/// Returns a slice of the remaining space in the header buffer
pub fn header_buffer_left(&self) -> &[u8] {
let off = self.header_buffer_offset();
&self.header_buffer()[off..]
}
/// Returns a mutable slice of the remaining unread portion of the header.
/// Returns a mutable slice of the remaining space in the header buffer
pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
let off = self.header_buffer_offset();
&mut self.header_buffer_mut()[off..]
}
/// Returns the currently read portion of the message.
/// Returns a slice of the available data in the message buffer
pub fn message_buffer_avail(&self) -> &[u8] {
let off = self.message_buffer_offset();
&self.message_buffer()[..off]
}
/// Returns a mutable slice of the currently read portion of the message.
/// Returns a mutable slice of the available data in the message buffer
pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
let off = self.message_buffer_offset();
&mut self.message_buffer_mut()[..off]
}
/// Returns the remaining unread portion of the message buffer.
/// Returns a slice of the remaining space in 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 unread portion of the message buffer.
/// Returns a mutable slice of the remaining space in 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 fully read.
/// Returns the message size from the header if available
pub fn get_header(&self) -> Option<usize> {
match self.header_buffer_offset() == HEADER_SIZE {
false => None,
@@ -505,23 +342,23 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
}
}
/// Returns the message size if known (i.e., if the header is fully read).
/// Returns the size of the message if header is available
pub fn message_size(&self) -> Option<usize> {
self.get_header()
}
/// Returns the total size of the encoded message (header + payload) if known.
/// Returns the total size of the encoded message including header
pub fn encoded_message_bytes(&self) -> Option<usize> {
self.message_size().map(|sz| sz + HEADER_SIZE)
}
/// Returns the complete message fragment if the header is known and buffer is sufficient.
/// Returns a slice of the message fragment if available
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 reference to the complete message fragment.
/// Returns a mutable slice of the message fragment if available
pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
self.ensure_sufficient_msg_buffer()?;
Ok(self
@@ -529,14 +366,14 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
.map(|sz| &mut self.message_buffer_mut()[..sz]))
}
/// Returns the portion of the message fragment that has been filled so far.
/// Returns a slice of the available data in the message fragment
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 portion of the message fragment that has been filled so far.
/// Returns a mutable slice of the available data in the message fragment
pub fn message_fragment_avail_mut(
&mut self,
) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
@@ -545,32 +382,28 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
.map(|frag| frag.map(|frag| &mut frag[..off]))
}
/// Returns the remaining portion of the message fragment that still needs to be read.
/// Returns a slice of the remaining space in the message fragment
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 portion of the message fragment that still needs to be read.
/// Returns a mutable slice of the remaining space in the message fragment
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..]))
}
/// If the entire message is available, returns a reference to it.
///
/// Otherwise returns `Ok(None)`.
/// Returns a slice of the complete message if available
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)))
}
/// If the entire message is available, returns a mutable reference to it.
///
/// Otherwise returns `Ok(None)`.
/// Returns a mutable slice of the complete message if available
pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
let sz = self.message_size();
self.message_fragment_avail_mut()
@@ -585,107 +418,3 @@ 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]);
}
}

View File

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

View File

@@ -1,33 +1,10 @@
//!
//! 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),+) => {{
@@ -45,37 +22,12 @@ macro_rules! cat {
}
// TODO: consistent inout ordering
/// 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]);
/// ```
/// Copy all bytes from `src` to `dst`. The lengths must match.
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.
///
/// # 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]);
/// ```
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
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();
@@ -83,30 +35,20 @@ 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
///
/// # 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");
/// ```
/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)]
pub struct Forgetting<T> {
value: Option<T>,
}
impl<T> Forgetting<T> {
/// Create a new `Forgetting` wrapping `value`.
/// Creates a new `Forgetting<T>` instance containing the given value.
pub fn new(value: T) -> Self {
Self { value: Some(value) }
let value = Some(value);
Self { value }
}
/// Consume and return the inner value.
/// Extracts and returns the contained value, consuming self.
pub fn extract(mut self) -> T {
let mut value = None;
swap(&mut value, &mut self.value);
@@ -155,13 +97,6 @@ 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);
@@ -172,16 +107,8 @@ 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 {
/// Forget the value.
/// Consumes and forgets a value, preventing its destructor from running.
fn forget(self);
}
@@ -192,23 +119,10 @@ 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 {
/// Swap values and return the old value of `self`.
/// Takes ownership of `other` and swaps its value with `self`, returning the original value.
fn swap_with(&mut self, other: Self) -> Self;
/// Swap values in place with another mutable reference.
/// Swaps the values between `self` and `other` in place.
fn swap_with_mut(&mut self, other: &mut Self);
}
@@ -224,18 +138,8 @@ 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 {
/// Swap with `Self::default()`.
/// Takes the current value and replaces it with the default value, returning the original.
fn swap_with_default(&mut self) -> Self;
}
@@ -246,26 +150,6 @@ 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
///
@@ -279,29 +163,3 @@ impl<T: Sized> MoveExt for T {
self
}
}
#[cfg(test)]
mod test_forgetting {
use crate::mem::Forgetting;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;
#[test]
fn test_forgetting() {
let drop_was_called = Arc::new(AtomicBool::new(false));
struct SetFlagOnDrop(Arc<AtomicBool>);
impl Drop for SetFlagOnDrop {
fn drop(&mut self) {
self.0.store(true, SeqCst);
}
}
drop(SetFlagOnDrop(drop_was_called.clone()));
assert!(drop_was_called.load(SeqCst));
// reset flag and use Forgetting
drop_was_called.store(false, SeqCst);
let forgetting = Forgetting::new(SetFlagOnDrop(drop_was_called.clone()));
drop(forgetting);
assert_eq!(drop_was_called.load(SeqCst), false);
}
}

View File

@@ -6,7 +6,7 @@ use crate::{
result::OkExt,
};
/// Module containing I/O interest flags for Unix operations (see also: [mio::Interest])
/// Module containing I/O interest flags for Unix operations
pub mod interest {
use mio::Interest;
@@ -20,48 +20,9 @@ pub mod interest {
pub const RW: Interest = R.add(W);
}
/// 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();
/// ```
/// Extension trait providing additional functionality for Unix listener
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>;
}
@@ -75,17 +36,15 @@ impl UnixListenerExt for UnixListener {
}
}
/// Extension trait providing additional functionality for a Unix stream
/// Extension trait providing additional functionality for Unix streams
pub trait UnixStreamExt: Sized {
/// Creates a new Unix stream from an owned file descriptor
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor and creates a new Unix stream
/// (see [fd::claim_fd](crate::fd::claim_fd))
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
/// (see [fd::claim_fd_inplace](crate::fd::claim_fd_inplace))
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
}

View File

@@ -12,58 +12,6 @@ use crate::fd::{claim_fd_inplace, IntoStdioErr};
/// A wrapper around a socket that combines reading from the socket with tracking
/// received file descriptors. Limits the maximum number of file descriptors that
/// can be received in a single read operation via the `MAX_FDS` parameter.
///
/// # Example
///
/// ```rust
/// use std::collections::VecDeque;
/// use std::io::Cursor;
/// use std::io::Read;
/// use std::os::fd::AsRawFd;
/// use std::os::fd::OwnedFd;
///
/// use mio::net::UnixStream;
/// use rosenpass_util::mio::ReadWithFileDescriptors;
/// use rosenpass_util::io::TryIoResultKindHintExt;
///
/// const MAX_REQUEST_FDS : usize = 2; // Limit to 2 descriptors per read operation
/// let mut read_fd_buffer = VecDeque::<OwnedFd>::new(); // File descriptor queue
///
/// // In this case, the unused writable end of the connection can be ignored
/// let (io_stream, _) = UnixStream::pair().expect("failed to create socket pair");
///
/// // Wait until the output stream is writable...
///
/// // Wrap the socket to start tracking received file descriptors
/// let mut fd_passing_sock = ReadWithFileDescriptors::<MAX_REQUEST_FDS, UnixStream, _, _>::new(
/// &io_stream,
/// &mut read_fd_buffer,
/// );
////
/// // Simulated reads; the actual operations will depend on the protocol (implementation details)
/// let mut recv_buffer = Vec::<u8>::new();
/// let bytes_read = fd_passing_sock.read(&mut recv_buffer[..]).expect("error reading from socket");
/// assert_eq!(bytes_read, 0);
/// assert_eq!(&recv_buffer[..bytes_read], []);
///
/// // Alternatively, it's possible to use the try_io_err_kind_hint utility provided by this crate
/// match fd_passing_sock.read(&mut recv_buffer).try_io_err_kind_hint() {
/// Err(_) => {
/// // Handle errors here ...
/// }
/// Ok(result) => {
/// // Process messages here ...
/// assert_eq!(0, result); // Nothing to read in this example
/// }
/// };
///
/// // The wrapped components can still be accessed
/// assert_eq!(fd_passing_sock.socket().as_raw_fd(), io_stream.as_raw_fd());
/// let (socket, fd_queue) = fd_passing_sock.into_parts();
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
///
/// // Shutdown, cleanup, etc. goes here ...
/// ```
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
where
Sock: FdPassingExt,

View File

@@ -1,24 +1,6 @@
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) => {
@@ -27,19 +9,6 @@ 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>;
@@ -57,11 +26,6 @@ impl<T, E> OkExt<E> for T {
/// the function will not panic.
///
/// Implementations must not panic.
/// # Examples
/// ```
/// # use rosenpass_util::result::GuaranteedValue;
/// let x:u32 = 10u8.try_into().guaranteed();
/// ```
pub trait GuaranteedValue {
/// The value type that will be returned by guaranteed()
type Value;

View File

@@ -17,7 +17,7 @@ use std::time::Instant;
/// ```
#[derive(Clone, Debug)]
pub struct Timebase(pub Instant);
pub struct Timebase(Instant);
impl Default for Timebase {
// TODO: Implement new()?

View File

@@ -3,23 +3,7 @@ use typenum::int::{NInt, PInt, Z0};
use typenum::marker_traits as markers;
use typenum::uint::{UInt, UTerm};
/// 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);
/// ```
/// Convenience macro to convert type numbers to constant integers
#[macro_export]
macro_rules! typenum2const {
($val:ty) => {
@@ -30,80 +14,35 @@ macro_rules! typenum2const {
};
}
/// 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);
/// ```
/// Trait implemented by constant integers to facilitate conversion to constant integers
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)]
/// 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);
struct ConstLshift<T, Param: IntoConst<T>, const SHIFT: i32>(*const T, *const Param); // impl IntoConst<T>
#[allow(dead_code)]
/// 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);
struct ConstAdd<T, Lhs: IntoConst<T>, Rhs: IntoConst<T>>(*const T, *const Lhs, *const Rhs); // impl IntoConst<T>
/// 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.
/// Assigns an unsigned type to a signed type
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 {
@@ -112,10 +51,6 @@ 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 }
@@ -138,10 +73,6 @@ 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 } )*
@@ -160,9 +91,6 @@ 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} )*
@@ -182,8 +110,9 @@ 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) {
// Handling negative values at boundaries, such as i8::MIN
<$type>::MIN
// Handle the negative value without an associated positive value (e.g. -128
// for i8)
< $type >::MIN
} else {
-(Param::VALUE as $type)
};
@@ -193,10 +122,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>,
@@ -204,28 +133,26 @@ where
ConstLshift<Ret, Rest, 1>: IntoConst<Ret>,
ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit>: IntoConst<Ret>,
{
/// Converts an unsigned [`UInt`] typenum into its corresponding constant integer by
/// decomposing it into shifts and additions on its subparts.
const VALUE: Ret = <ConstAdd<Ret, ConstLshift<Ret, Rest, 1>, Bit> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with positive sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for PInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyPosSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a positive signed [`PInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyPosSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}
// Signed type numbers with negative sign to const integers
impl<Ret, Unsigned> IntoConst<Ret> for NInt<Unsigned>
where
Ret: AssociatedUnsigned,
Unsigned: markers::Unsigned + markers::NonZero + IntoConst<<Ret as AssociatedUnsigned>::Type>,
ConstApplyNegSign<Ret, Unsigned>: IntoConst<Ret>,
{
/// Converts a negative signed [`NInt`] typenum into its corresponding constant integer.
const VALUE: Ret = <ConstApplyNegSign<Ret, Unsigned> as IntoConst<Ret>>::VALUE;
}

View File

@@ -1,20 +1,3 @@
//! 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;

View File

@@ -1,206 +1,74 @@
//! A module providing the [`RefMaker`] type and its associated methods for constructing
//! [`zerocopy::Ref`] references from byte buffers.
use std::marker::PhantomData;
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.
///
/// # Example
///
/// ```
/// # use rosenpass_util::zerocopy::RefMaker;
/// let buffer = [0u8; 10];
/// let rm: RefMaker<_, u32> = RefMaker::new(buffer);
/// ```
/// Creates a new RefMaker with the given buffer
pub fn new(buf: B) -> Self {
let _phantom_t = PhantomData;
Self { buf, _phantom_t }
}
/// Returns the size in bytes required by the target type `T`.
/// This is currently defined as [`std::mem::size_of::<T>`] of `T`.
/// Returns the size in bytes needed for target type 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 [`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!");
/// ```
/// Parses the buffer into a reference of type T
pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
self.ensure_fit()?;
Ref::<B, T>::new(self.buf).context("Parser error!")
}
/// 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]);
/// ```
/// Splits the buffer into a RefMaker containing the first `target_size` bytes and the remaining tail
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 `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]);
/// ```
/// Splits the buffer into two RefMakers, with the first containing the first `target_size` bytes
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 `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]);
/// ```
/// Returns a RefMaker containing only the first `target_size` bytes
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 `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]);
/// ```
/// Splits the buffer into a RefMaker containing the last `target_size` bytes and the preceding head
pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> {
self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size();
@@ -208,22 +76,7 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
Ok((Self::new(tail), head))
}
/// 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]);
/// ```
/// Splits the buffer into two RefMakers, with the second containing the last `target_size` bytes
pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
self.ensure_fit()?;
let point = self.bytes().len() - Self::target_size();
@@ -231,46 +84,17 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
Ok((Self::new(head), Self::new(tail)))
}
/// 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]);
/// ```
/// Returns a RefMaker containing only the last `target_size` bytes
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 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());
/// ```
/// Ensures the buffer is large enough to hold type T
pub fn ensure_fit(&self) -> anyhow::Result<()> {
let have = self.bytes().len();
let need = Self::target_size();
@@ -283,30 +107,12 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
}
impl<B: ByteSliceMut, T> RefMaker<B, T> {
/// 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]);
/// ```
/// Creates a zeroed reference of type T from the buffer
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()
}

View File

@@ -1,64 +1,14 @@
//! 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 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);
/// ```
/// Converts this reference into a reference backed by a byte slice.
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 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]);
/// ```
/// Converts this reference into a mutable reference backed by a byte slice.
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
}

View File

@@ -1,113 +1,25 @@
//! Extension traits for parsing slices into [`zerocopy::Ref`] values using the
//! [`RefMaker`] abstraction.
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::RefMaker;
/// 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.
/// Extension trait for zero-copy slice operations.
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 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);
/// ```
/// Parses the slice into a zero-copy reference.
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()
}
@@ -115,90 +27,19 @@ pub trait ZerocopySliceExt: Sized + ByteSlice {
impl<B: ByteSlice> ZerocopySliceExt for B {}
/// Extension trait for zero-copy parsing of mutable slices with zeroization
/// capabilities.
///
/// Provides convenience methods to create zero-initialized references.
/// Extension trait for zero-copy slice operations with mutable slices.
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
/// 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]);
/// ```
/// Creates a new zeroed reference from the entire slice.
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().make_zeroized()
}
/// 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]);
/// ```
/// Creates a new zeroed reference from a prefix of the slice.
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_prefix()?.make_zeroized()
}
/// 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]);
/// ```
/// Creates a new zeroed reference from a suffix of the slice.
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
self.zk_ref_maker().from_suffix()?.make_zeroized()
}

View File

@@ -1,23 +1,2 @@
//!
//! 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::*;

View File

@@ -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.42", optional = true }
rustix = { version = "0.38.41", optional = true }
libc = { version = "0.2", optional = true }
# Mio broker client