mirror of
https://github.com/rosenpass/rosenpass.git
synced 2026-02-27 14:03:11 -08:00
Compare commits
12 Commits
dev/flake-
...
dev/update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e2cd589b1 | ||
|
|
02bc485d97 | ||
|
|
3ae52b9824 | ||
|
|
cbf361206b | ||
|
|
398da99df2 | ||
|
|
acfbb67abe | ||
|
|
c407b8b006 | ||
|
|
bc7213d8c0 | ||
|
|
69e68aad2c | ||
|
|
9b07f5803b | ||
|
|
5ce572b739 | ||
|
|
d9f8fa0092 |
@@ -1,4 +1,5 @@
|
||||
.direnv/
|
||||
flake.lock
|
||||
papers/whitepaper.md
|
||||
target/
|
||||
src/usage.md
|
||||
target/
|
||||
|
||||
534
Cargo.lock
generated
534
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ 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.19", features = ["derive"] }
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||
anyhow = { version = "1.0.89", features = ["backtrace", "std"] }
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
//! Constant-time comparison
|
||||
|
||||
use core::ptr;
|
||||
|
||||
/// 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.
|
||||
#[inline(never)]
|
||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
let mut res = 0;
|
||||
@@ -13,6 +21,16 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
((res - 1) >> 8) + (res >> 8) + 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn memcmp_le_test() {
|
||||
// use rosenpass_constant_time::memcmp_le;
|
||||
let a = [0, 1, 0, 0];
|
||||
let b = [0, 0, 0, 1];
|
||||
assert_eq!(-1, unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), 4) });
|
||||
assert_eq!(0, unsafe { memcmp_le(a.as_ptr(), a.as_ptr(), 4) });
|
||||
assert_eq!(1, unsafe { memcmp_le(b.as_ptr(), a.as_ptr(), 4) });
|
||||
}
|
||||
|
||||
/// compares two slices of memory content and returns an integer indicating the relationship between
|
||||
/// the slices
|
||||
///
|
||||
@@ -32,6 +50,28 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||
/// ## Tests
|
||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_constant_time::compare;
|
||||
/// let a = [0, 1, 0, 0];
|
||||
/// let b = [0, 0, 0, 1];
|
||||
/// assert_eq!(-1, compare(&a, &b));
|
||||
/// assert_eq!(0, compare(&a, &a));
|
||||
/// assert_eq!(1, compare(&b, &a));
|
||||
/// ```
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This function will panic if the input arrays are of different lengths.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use rosenpass_constant_time::compare;
|
||||
/// let a = [0, 1, 0];
|
||||
/// let b = [0, 0, 0, 1];
|
||||
/// compare(&a, &b);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Incrementing numbers
|
||||
|
||||
use core::hint::black_box;
|
||||
|
||||
/// Interpret the given slice as a little-endian unsigned integer
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
//! constant-time implementations of some primitives
|
||||
//!
|
||||
//! Rosenpass internal library providing basic constant-time operations.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! memcmp
|
||||
|
||||
/// compares two sclices of memory content and returns whether they are equal
|
||||
///
|
||||
/// ## Leaks
|
||||
@@ -7,6 +9,18 @@
|
||||
///
|
||||
/// The execution time of the function grows approx. linear with the length of the input. This is
|
||||
/// considered safe.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rosenpass_constant_time::memcmp;
|
||||
/// let a = [0, 0, 0, 0];
|
||||
/// let b = [0, 0, 0, 1];
|
||||
/// let c = [0, 0, 0];
|
||||
/// assert!(memcmp(&a, &a));
|
||||
/// assert!(!memcmp(&a, &b));
|
||||
/// assert!(!memcmp(&a, &c));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! xor
|
||||
|
||||
use core::hint::black_box;
|
||||
use rosenpass_to::{with_destination, To};
|
||||
|
||||
|
||||
49
flake.lock
generated
49
flake.lock
generated
@@ -2,15 +2,17 @@
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": ["nixpkgs"],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712298178,
|
||||
"narHash": "sha256-590fpCPXYAkaAeBz/V91GX4/KGzPObdYtqsTWzT6AhI=",
|
||||
"lastModified": 1728282832,
|
||||
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "569b5b5781395da08e7064e825953c548c26af76",
|
||||
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -24,11 +26,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -37,36 +39,18 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": ["nixpkgs"]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698420672,
|
||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1712168706,
|
||||
"narHash": "sha256-XP24tOobf6GGElMd0ux90FEBalUtw6NkBSVh/RlA6ik=",
|
||||
"lastModified": 1728193676,
|
||||
"narHash": "sha256-PbDWAIjKJdlVg+qQRhzdSor04bAPApDqIv2DofTyynk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1487bdea619e4a7a53a4590c475deabb5a9d1bfb",
|
||||
"rev": "ecbc1ca8ffd6aea8372ad16be9ebbb39889e55b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -75,18 +59,17 @@
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1712156296,
|
||||
"narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=",
|
||||
"lastModified": 1728249780,
|
||||
"narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "8e581ac348e223488622f4d3003cb2bd412bf27e",
|
||||
"rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
410
flake.nix
410
flake.nix
@@ -1,12 +1,8 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
# for quicker rust builds
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
# for rust nightly with llvm-tools-preview
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -15,6 +11,15 @@
|
||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
||||
|
||||
|
||||
#
|
||||
### Export the overlay.nix from this flake ###
|
||||
#
|
||||
{
|
||||
overlays.default = import ./overlay.nix;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
### Actual Rosenpass Package and Docker Container Images ###
|
||||
#
|
||||
@@ -30,310 +35,39 @@
|
||||
]
|
||||
(system:
|
||||
let
|
||||
scoped = (scope: scope.result);
|
||||
lib = nixpkgs.lib;
|
||||
|
||||
# normal nixpkgs
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
|
||||
# parsed Cargo.toml
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
||||
|
||||
# source files relevant for rust
|
||||
src = scoped rec {
|
||||
# File suffices to include
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
files = [
|
||||
"to/README.md"
|
||||
];
|
||||
|
||||
src = ./.;
|
||||
filter = (path: type: scoped rec {
|
||||
inherit (lib) any id removePrefix hasSuffix;
|
||||
anyof = (any id);
|
||||
|
||||
basename = baseNameOf (toString path);
|
||||
relative = removePrefix (toString src + "/") (toString path);
|
||||
|
||||
result = anyof [
|
||||
(type == "directory")
|
||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||
(any (file: file == relative) files)
|
||||
];
|
||||
});
|
||||
|
||||
result = pkgs.lib.sources.cleanSourceWith { inherit src filter; };
|
||||
};
|
||||
|
||||
# a function to generate a nix derivation for rosenpass against any
|
||||
# given set of nixpkgs
|
||||
rosenpassDerivation = p:
|
||||
let
|
||||
# whether we want to build a statically linked binary
|
||||
isStatic = p.targetPlatform.isStatic;
|
||||
|
||||
# the rust target of `p`
|
||||
target = p.rust.toRustTargetSpec p.targetPlatform;
|
||||
|
||||
# convert a string to shout case
|
||||
shout = string: builtins.replaceStrings [ "-" ] [ "_" ] (pkgs.lib.toUpper string);
|
||||
|
||||
# suitable Rust toolchain
|
||||
toolchain = with inputs.fenix.packages.${system}; combine [
|
||||
stable.cargo
|
||||
stable.rustc
|
||||
targets.${target}.stable.rust-std
|
||||
];
|
||||
|
||||
# naersk with a custom toolchain
|
||||
naersk = pkgs.callPackage inputs.naersk {
|
||||
cargo = toolchain;
|
||||
rustc = toolchain;
|
||||
};
|
||||
|
||||
# used to trick the build.rs into believing that CMake was ran **again**
|
||||
fakecmake = pkgs.writeScriptBin "cmake" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
true
|
||||
'';
|
||||
in
|
||||
naersk.buildPackage
|
||||
{
|
||||
# metadata and source
|
||||
name = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
inherit src;
|
||||
|
||||
cargoBuildOptions = x: x ++ [ "-p" "rosenpass" ];
|
||||
cargoTestOptions = x: x ++ [ "-p" "rosenpass" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
p.stdenv.cc
|
||||
cmake # for oqs build in the oqs-sys crate
|
||||
mandoc # for the built-in manual
|
||||
removeReferencesTo
|
||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||
];
|
||||
buildInputs = with p; [ bash ];
|
||||
|
||||
override = x: {
|
||||
preBuild =
|
||||
# nix defaults to building for aarch64 _without_ the armv8-a crypto
|
||||
# extensions, but liboqs depens on these
|
||||
(lib.optionalString (system == "aarch64-linux") ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"
|
||||
''
|
||||
);
|
||||
|
||||
# fortify is only compatible with dynamic linking
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
};
|
||||
|
||||
overrideMain = x: {
|
||||
# CMake detects that it was served a _foreign_ target dir, and CMake
|
||||
# would be executed again upon the second build step of naersk.
|
||||
# By adding our specially optimized CMake version, we reduce the cost
|
||||
# of recompilation by 99 % while, while avoiding any CMake errors.
|
||||
nativeBuildInputs = [ (lib.hiPrio fakecmake) ] ++ x.nativeBuildInputs;
|
||||
|
||||
# make sure that libc is linked, under musl this is not the case per
|
||||
# default
|
||||
preBuild = (lib.optionalString isStatic ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -lc"
|
||||
'');
|
||||
};
|
||||
|
||||
# We want to build for a specific target...
|
||||
CARGO_BUILD_TARGET = target;
|
||||
|
||||
# ... which might require a non-default linker:
|
||||
"CARGO_TARGET_${shout target}_LINKER" =
|
||||
let
|
||||
inherit (p.stdenv) cc;
|
||||
in
|
||||
"${cc}/bin/${cc.targetPrefix}cc";
|
||||
|
||||
meta = with pkgs.lib;
|
||||
{
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with licenses; [ mit asl20 ];
|
||||
maintainers = [ maintainers.wucke13 ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
} // (lib.mkIf isStatic {
|
||||
# otherwise pkg-config tries to link non-existent dynamic libs
|
||||
# documented here: https://docs.rs/pkg-config/latest/pkg_config/
|
||||
PKG_CONFIG_ALL_STATIC = true;
|
||||
|
||||
# tell rust to build everything statically linked
|
||||
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
||||
});
|
||||
# a function to generate a nix derivation for the rp helper against any
|
||||
# given set of nixpkgs
|
||||
rpDerivation = p:
|
||||
let
|
||||
# whether we want to build a statically linked binary
|
||||
isStatic = p.targetPlatform.isStatic;
|
||||
|
||||
# the rust target of `p`
|
||||
target = p.rust.toRustTargetSpec p.targetPlatform;
|
||||
|
||||
# convert a string to shout case
|
||||
shout = string: builtins.replaceStrings [ "-" ] [ "_" ] (pkgs.lib.toUpper string);
|
||||
|
||||
# suitable Rust toolchain
|
||||
toolchain = with inputs.fenix.packages.${system}; combine [
|
||||
stable.cargo
|
||||
stable.rustc
|
||||
targets.${target}.stable.rust-std
|
||||
];
|
||||
|
||||
# naersk with a custom toolchain
|
||||
naersk = pkgs.callPackage inputs.naersk {
|
||||
cargo = toolchain;
|
||||
rustc = toolchain;
|
||||
};
|
||||
|
||||
# used to trick the build.rs into believing that CMake was ran **again**
|
||||
fakecmake = pkgs.writeScriptBin "cmake" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
true
|
||||
'';
|
||||
in
|
||||
naersk.buildPackage
|
||||
{
|
||||
# metadata and source
|
||||
name = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
inherit src;
|
||||
|
||||
cargoBuildOptions = x: x ++ [ "-p" "rp" ];
|
||||
cargoTestOptions = x: x ++ [ "-p" "rp" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
p.stdenv.cc
|
||||
cmake # for oqs build in the oqs-sys crate
|
||||
mandoc # for the built-in manual
|
||||
removeReferencesTo
|
||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||
];
|
||||
buildInputs = with p; [ bash ];
|
||||
|
||||
override = x: {
|
||||
preBuild =
|
||||
# nix defaults to building for aarch64 _without_ the armv8-a crypto
|
||||
# extensions, but liboqs depens on these
|
||||
(lib.optionalString (system == "aarch64-linux") ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"
|
||||
''
|
||||
);
|
||||
|
||||
# fortify is only compatible with dynamic linking
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
};
|
||||
|
||||
overrideMain = x: {
|
||||
# CMake detects that it was served a _foreign_ target dir, and CMake
|
||||
# would be executed again upon the second build step of naersk.
|
||||
# By adding our specially optimized CMake version, we reduce the cost
|
||||
# of recompilation by 99 % while, while avoiding any CMake errors.
|
||||
nativeBuildInputs = [ (lib.hiPrio fakecmake) ] ++ x.nativeBuildInputs;
|
||||
|
||||
# make sure that libc is linked, under musl this is not the case per
|
||||
# default
|
||||
preBuild = (lib.optionalString isStatic ''
|
||||
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -lc"
|
||||
'');
|
||||
};
|
||||
|
||||
# We want to build for a specific target...
|
||||
CARGO_BUILD_TARGET = target;
|
||||
|
||||
# ... which might require a non-default linker:
|
||||
"CARGO_TARGET_${shout target}_LINKER" =
|
||||
let
|
||||
inherit (p.stdenv) cc;
|
||||
in
|
||||
"${cc}/bin/${cc.targetPrefix}cc";
|
||||
|
||||
meta = with pkgs.lib;
|
||||
{
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with licenses; [ mit asl20 ];
|
||||
maintainers = [ maintainers.wucke13 ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
} // (lib.mkIf isStatic {
|
||||
# otherwise pkg-config tries to link non-existent dynamic libs
|
||||
# documented here: https://docs.rs/pkg-config/latest/pkg_config/
|
||||
PKG_CONFIG_ALL_STATIC = true;
|
||||
|
||||
# tell rust to build everything statically linked
|
||||
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
|
||||
});
|
||||
# a function to generate a docker image based of rosenpass
|
||||
rosenpassOCI = name: pkgs.dockerTools.buildImage rec {
|
||||
inherit name;
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = [ self.packages.${system}.${name} ];
|
||||
pathsToLink = [ "/bin" ];
|
||||
};
|
||||
config.Cmd = [ "/bin/rosenpass" ];
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
in
|
||||
rec {
|
||||
packages = rec {
|
||||
default = rosenpass;
|
||||
rosenpass = rosenpassDerivation pkgs;
|
||||
rp = rpDerivation pkgs;
|
||||
rosenpass-oci-image = rosenpassOCI "rosenpass";
|
||||
{
|
||||
packages = {
|
||||
default = pkgs.rosenpass;
|
||||
rosenpass = pkgs.rosenpass;
|
||||
rosenpass-oci-image = pkgs.rosenpass-oci-image;
|
||||
rp = pkgs.rp;
|
||||
|
||||
# derivation for the release
|
||||
release-package =
|
||||
let
|
||||
version = cargoToml.package.version;
|
||||
package =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rosenpass-static
|
||||
else packages.rosenpass;
|
||||
rp =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rp-static
|
||||
else packages.rp;
|
||||
oci-image =
|
||||
if pkgs.hostPlatform.isLinux then
|
||||
packages.rosenpass-static-oci-image
|
||||
else packages.rosenpass-oci-image;
|
||||
in
|
||||
pkgs.runCommandNoCC "lace-result" { }
|
||||
''
|
||||
mkdir {bin,$out}
|
||||
tar -cvf $out/rosenpass-${system}-${version}.tar \
|
||||
-C ${package} bin/rosenpass \
|
||||
-C ${rp} bin/rp
|
||||
cp ${oci-image} \
|
||||
$out/rosenpass-oci-image-${system}-${version}.tar.gz
|
||||
'';
|
||||
} // (if pkgs.stdenv.isLinux then rec {
|
||||
rosenpass-static = rosenpassDerivation pkgs.pkgsStatic;
|
||||
rp-static = rpDerivation pkgs.pkgsStatic;
|
||||
rosenpass-static-oci-image = rosenpassOCI "rosenpass-static";
|
||||
} else { });
|
||||
release-package = pkgs.release-package;
|
||||
|
||||
# for good measure, we also offer to cross compile to Linux on Arm
|
||||
aarch64-linux-rosenpass-static =
|
||||
pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
|
||||
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
|
||||
}
|
||||
//
|
||||
# We only offer static builds for linux, as this is not supported on OS X
|
||||
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
|
||||
rosenpass-static = pkgs.pkgsStatic.rosenpass;
|
||||
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
|
||||
rp-static = pkgs.pkgsStatic.rp;
|
||||
});
|
||||
}
|
||||
))
|
||||
|
||||
|
||||
#
|
||||
### Linux specifics ###
|
||||
#
|
||||
@@ -341,88 +75,46 @@
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
packages = self.packages.${system};
|
||||
in
|
||||
{
|
||||
#
|
||||
### Whitepaper ###
|
||||
#
|
||||
packages.whitepaper =
|
||||
let
|
||||
tlsetup = (pkgs.texlive.combine {
|
||||
inherit (pkgs.texlive) scheme-basic acmart amsfonts ccicons
|
||||
csquotes csvsimple doclicense fancyvrb fontspec gobble
|
||||
koma-script ifmtarg latexmk lm markdown mathtools minted noto
|
||||
nunito pgf soul unicode-math lualatex-math paralist
|
||||
gitinfo2 eso-pic biblatex biblatex-trad biblatex-software
|
||||
xkeyval xurl xifthen biber;
|
||||
});
|
||||
in
|
||||
pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "whitepaper";
|
||||
src = ./papers;
|
||||
nativeBuildInputs = with pkgs; [
|
||||
ncurses # tput
|
||||
python3Packages.pygments
|
||||
tlsetup # custom tex live scheme
|
||||
which
|
||||
];
|
||||
buildPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
latexmk -r tex/CI.rc
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mv *.pdf readme.md $out/
|
||||
'';
|
||||
};
|
||||
|
||||
#
|
||||
### Reading materials ###
|
||||
#
|
||||
packages.whitepaper = pkgs.whitepaper;
|
||||
|
||||
#
|
||||
### Proof and Proof Tools ###
|
||||
#
|
||||
packages.proverif-patched = pkgs.proverif.overrideAttrs (old: {
|
||||
postInstall = ''
|
||||
install -D -t $out/lib cryptoverif.pvl
|
||||
'';
|
||||
});
|
||||
packages.proof-proverif = pkgs.stdenv.mkDerivation {
|
||||
name = "rosenpass-proverif-proof";
|
||||
version = "unstable";
|
||||
src = pkgs.lib.sources.sourceByRegex ./. [
|
||||
"analyze.sh"
|
||||
"marzipan(/marzipan.awk)?"
|
||||
"analysis(/.*)?"
|
||||
];
|
||||
nativeBuildInputs = [ pkgs.proverif pkgs.graphviz ];
|
||||
CRYPTOVERIF_LIB = packages.proverif-patched + "/lib/cryptoverif.pvl";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
bash analyze.sh -color -html $out
|
||||
'';
|
||||
};
|
||||
packages.proverif-patched = pkgs.proverif-patched;
|
||||
packages.proof-proverif = pkgs.proof-proverif;
|
||||
|
||||
|
||||
#
|
||||
### Devshells ###
|
||||
#
|
||||
devShells.default = pkgs.mkShell {
|
||||
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ packages.default ];
|
||||
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
cmake # override the fakecmake from the main step above
|
||||
cargo-release
|
||||
clippy
|
||||
rustfmt
|
||||
nodePackages.prettier
|
||||
nushell # for the .ci/gen-workflow-files.nu script
|
||||
packages.proverif-patched
|
||||
proverif-patched
|
||||
];
|
||||
};
|
||||
devShells.coverage = pkgs.mkShell {
|
||||
inputsFrom = [ packages.default ];
|
||||
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
|
||||
inputsFrom = [ pkgs.rosenpass ];
|
||||
nativeBuildInputs = [
|
||||
inputs.fenix.packages.${system}.complete.toolchain
|
||||
pkgs.cargo-llvm-cov
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -14,3 +14,7 @@ rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
oqs-sys = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-constant-time = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,42 @@
|
||||
//! Generic helpers for declaring bindings to liboqs kems
|
||||
|
||||
/// Generate bindings to a liboqs-provided KEM
|
||||
macro_rules! oqs_kem {
|
||||
($name:ident) => { ::paste::paste!{
|
||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||
mod [< $name:snake >] {
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_util::result::Guaranteed;
|
||||
|
||||
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||
#[doc = ""]
|
||||
#[doc = "# Examples"]
|
||||
#[doc = ""]
|
||||
#[doc = "```rust"]
|
||||
#[doc = "use std::borrow::{Borrow, BorrowMut};"]
|
||||
#[doc = "use rosenpass_cipher_traits::Kem;"]
|
||||
#[doc = "use rosenpass_oqs::" $name:camel " as MyKem;"]
|
||||
#[doc = "use rosenpass_secret_memory::{Secret, Public};"]
|
||||
#[doc = ""]
|
||||
#[doc = "rosenpass_secret_memory::secret_policy_try_use_memfd_secrets();"]
|
||||
#[doc = ""]
|
||||
#[doc = "// Recipient generates secret key, transfers pk to sender"]
|
||||
#[doc = "let mut sk = Secret::<{ MyKem::SK_LEN }>::zero();"]
|
||||
#[doc = "let mut pk = Public::<{ MyKem::PK_LEN }>::zero();"]
|
||||
#[doc = "MyKem::keygen(sk.secret_mut(), pk.borrow_mut());"]
|
||||
#[doc = ""]
|
||||
#[doc = "// Sender generates ciphertext and local shared key, sends ciphertext to recipient"]
|
||||
#[doc = "let mut shk_enc = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||
#[doc = "let mut ct = Public::<{ MyKem::CT_LEN }>::zero();"]
|
||||
#[doc = "MyKem::encaps(shk_enc.secret_mut(), ct.borrow_mut(), pk.borrow());"]
|
||||
#[doc = ""]
|
||||
#[doc = "// Recipient decapsulates ciphertext"]
|
||||
#[doc = "let mut shk_dec = Secret::<{ MyKem::SHK_LEN }>::zero();"]
|
||||
#[doc = "MyKem::decaps(shk_dec.secret_mut(), sk.secret(), ct.borrow());"]
|
||||
#[doc = ""]
|
||||
#[doc = "// Both parties end up with the same shared key"]
|
||||
#[doc = "assert!(rosenpass_constant_time::compare(shk_enc.secret_mut(), shk_dec.secret_mut()) == 0);"]
|
||||
#[doc = "```"]
|
||||
pub enum [< $name:camel >] {}
|
||||
|
||||
/// # Panic & Safety
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
//! Bindings for liboqs used in Rosenpass
|
||||
|
||||
/// Call into a libOQS function
|
||||
macro_rules! oqs_call {
|
||||
($name:path, $($args:expr),*) => {{
|
||||
use oqs_sys::common::OQS_STATUS::*;
|
||||
|
||||
39
overlay.nix
Normal file
39
overlay.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
final: prev: {
|
||||
|
||||
|
||||
#
|
||||
### Actual rosenpass software ###
|
||||
#
|
||||
rosenpass = final.callPackage ./pkgs/rosenpass.nix { };
|
||||
rosenpass-oci-image = final.callPackage ./pkgs/rosenpass-oci-image.nix { };
|
||||
rp = final.callPackage ./pkgs/rosenpass.nix { package = "rp"; };
|
||||
|
||||
release-package = final.callPackage ./pkgs/release-package.nix { };
|
||||
|
||||
#
|
||||
### Appendix ###
|
||||
#
|
||||
proverif-patched = prev.proverif.overrideAttrs (old: {
|
||||
postInstall = ''
|
||||
install -D -t $out/lib cryptoverif.pvl
|
||||
'';
|
||||
});
|
||||
|
||||
proof-proverif = final.stdenv.mkDerivation {
|
||||
name = "rosenpass-proverif-proof";
|
||||
version = "unstable";
|
||||
src = final.lib.sources.sourceByRegex ./. [
|
||||
"analyze.sh"
|
||||
"marzipan(/marzipan.awk)?"
|
||||
"analysis(/.*)?"
|
||||
];
|
||||
nativeBuildInputs = [ final.proverif final.graphviz ];
|
||||
CRYPTOVERIF_LIB = final.proverif-patched + "/lib/cryptoverif.pvl";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
bash analyze.sh -color -html $out
|
||||
'';
|
||||
};
|
||||
|
||||
whitepaper = final.callPackage ./pkgs/whitepaper.nix { };
|
||||
}
|
||||
27
pkgs/release-package.nix
Normal file
27
pkgs/release-package.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ lib, stdenvNoCC, runCommandNoCC, pkgsStatic, rosenpass, rosenpass-oci-image, rp } @ args:
|
||||
|
||||
let
|
||||
version = rosenpass.version;
|
||||
|
||||
# select static packages on Linux, default packages otherwise
|
||||
package =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rosenpass
|
||||
else args.rosenpass;
|
||||
rp =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rp
|
||||
else args.rp;
|
||||
oci-image =
|
||||
if stdenvNoCC.hostPlatform.isLinux then
|
||||
pkgsStatic.rosenpass-oci-image
|
||||
else args.rosenpass-oci-image;
|
||||
in
|
||||
runCommandNoCC "lace-result" { } ''
|
||||
mkdir {bin,$out}
|
||||
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
||||
-C ${package} bin/rosenpass \
|
||||
-C ${rp} bin/rp
|
||||
cp ${oci-image} \
|
||||
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
||||
''
|
||||
11
pkgs/rosenpass-oci-image.nix
Normal file
11
pkgs/rosenpass-oci-image.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ dockerTools, buildEnv, rosenpass }:
|
||||
|
||||
dockerTools.buildImage {
|
||||
name = rosenpass.name + "-oci";
|
||||
copyToRoot = buildEnv {
|
||||
name = "image-root";
|
||||
paths = [ rosenpass ];
|
||||
pathsToLink = [ "/bin" ];
|
||||
};
|
||||
config.Cmd = [ "/bin/rosenpass" ];
|
||||
}
|
||||
78
pkgs/rosenpass.nix
Normal file
78
pkgs/rosenpass.nix
Normal file
@@ -0,0 +1,78 @@
|
||||
{ lib, stdenv, rustPlatform, cmake, mandoc, removeReferencesTo, bash, package ? "rosenpass" }:
|
||||
|
||||
let
|
||||
# whether we want to build a statically linked binary
|
||||
isStatic = stdenv.targetPlatform.isStatic;
|
||||
|
||||
scoped = (scope: scope.result);
|
||||
|
||||
# source files relevant for rust
|
||||
src = scoped rec {
|
||||
# File suffices to include
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
files = [
|
||||
"to/README.md"
|
||||
];
|
||||
|
||||
src = ../.;
|
||||
filter = (path: type: scoped rec {
|
||||
inherit (lib) any id removePrefix hasSuffix;
|
||||
anyof = (any id);
|
||||
|
||||
basename = baseNameOf (toString path);
|
||||
relative = removePrefix (toString src + "/") (toString path);
|
||||
|
||||
result = anyof [
|
||||
(type == "directory")
|
||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||
(any (file: file == relative) files)
|
||||
];
|
||||
});
|
||||
|
||||
result = lib.sources.cleanSourceWith { inherit src filter; };
|
||||
};
|
||||
|
||||
# parsed Cargo.toml
|
||||
cargoToml = builtins.fromTOML (builtins.readFile (src + "/rosenpass/Cargo.toml"));
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
name = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
inherit src;
|
||||
|
||||
cargoBuildOptions = [ "--package" package ];
|
||||
cargoTestOptions = [ "--package" package ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
cargoLock = {
|
||||
lockFile = src + "/Cargo.lock";
|
||||
outputHashes = {
|
||||
"memsec-0.6.3" = "sha256-4ri+IEqLd77cLcul3lZrmpDKj4cwuYJ8oPRAiQNGeLw=";
|
||||
"uds-0.4.2" = "sha256-qlxr/iJt2AV4WryePIvqm/8/MK/iqtzegztNliR93W8=";
|
||||
};
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
stdenv.cc
|
||||
cmake # for oqs build in the oqs-sys crate
|
||||
mandoc # for the built-in manual
|
||||
removeReferencesTo
|
||||
rustPlatform.bindgenHook # for C-bindings in the crypto libs
|
||||
];
|
||||
buildInputs = [ bash ];
|
||||
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
|
||||
meta = {
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with lib.licenses; [ mit asl20 ];
|
||||
maintainers = [ lib.maintainers.wucke13 ];
|
||||
platforms = lib.platforms.all;
|
||||
};
|
||||
}
|
||||
29
pkgs/whitepaper.nix
Normal file
29
pkgs/whitepaper.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{ stdenvNoCC, texlive, ncurses, python3Packages, which }:
|
||||
|
||||
let
|
||||
customTexLiveSetup = (texlive.combine {
|
||||
inherit (texlive) acmart amsfonts biber biblatex biblatex-software
|
||||
biblatex-trad ccicons csquotes csvsimple doclicense eso-pic fancyvrb
|
||||
fontspec gitinfo2 gobble ifmtarg koma-script latexmk lm lualatex-math
|
||||
markdown mathtools minted noto nunito paralist pgf scheme-basic soul
|
||||
unicode-math upquote xifthen xkeyval xurl;
|
||||
});
|
||||
in
|
||||
stdenvNoCC.mkDerivation {
|
||||
name = "whitepaper";
|
||||
src = ../papers;
|
||||
nativeBuildInputs = [
|
||||
ncurses # tput
|
||||
python3Packages.pygments
|
||||
customTexLiveSetup # custom tex live scheme
|
||||
which
|
||||
];
|
||||
buildPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
latexmk -r tex/CI.rc
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
mv *.pdf readme.md $out/
|
||||
'';
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rosenpass"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0-dev"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -154,7 +154,6 @@ pub struct AppServerTest {
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum AppServerIoSource {
|
||||
Socket(usize),
|
||||
#[cfg(feature = "experiment_api")]
|
||||
PskBroker(Public<BROKER_ID_BYTES>),
|
||||
#[cfg(feature = "experiment_api")]
|
||||
MioManager(crate::api::mio::MioManagerIoSource),
|
||||
@@ -1209,15 +1208,12 @@ impl AppServer {
|
||||
buf: &mut [u8],
|
||||
io_source: AppServerIoSource,
|
||||
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||
use crate::api::mio::MioManagerContext;
|
||||
|
||||
match io_source {
|
||||
AppServerIoSource::Socket(idx) => self
|
||||
.try_recv_from_listen_socket(buf, idx)
|
||||
.substitute_for_ioerr_wouldblock(None)?
|
||||
.ok(),
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
AppServerIoSource::PskBroker(key) => self
|
||||
.brokers
|
||||
.store
|
||||
@@ -1227,9 +1223,13 @@ impl AppServer {
|
||||
.map(|_| None),
|
||||
|
||||
#[cfg(feature = "experiment_api")]
|
||||
AppServerIoSource::MioManager(mmio_src) => MioManagerFocus(self)
|
||||
.poll_particular(mmio_src)
|
||||
.map(|_| None),
|
||||
AppServerIoSource::MioManager(mmio_src) => {
|
||||
use crate::api::mio::MioManagerContext;
|
||||
|
||||
MioManagerFocus(self)
|
||||
.poll_particular(mmio_src)
|
||||
.map(|_| None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
//! Utilities for working with Base64
|
||||
|
||||
use base64ct::{Base64, Decoder as B64Reader, Encoder as B64Writer};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Formatter that displays its input as base64.
|
||||
///
|
||||
/// Use through [B64Display].
|
||||
pub struct B64DisplayHelper<'a, const F: usize>(&'a [u8]);
|
||||
|
||||
impl<const F: usize> Display for B64DisplayHelper<'_, F> {
|
||||
@@ -15,7 +20,25 @@ impl<const F: usize> Display for B64DisplayHelper<'_, F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait that can be used to display values as Base64
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::b64::B64Display;
|
||||
///
|
||||
/// let a = vec![0,1,2,3,4,5];
|
||||
/// assert_eq!(
|
||||
/// format!("{}", a.fmt_b64::<10>()), // Maximum size of the encoded buffer
|
||||
/// "AAECAwQF",
|
||||
/// );
|
||||
/// ```
|
||||
pub trait B64Display {
|
||||
/// Display this value as base64
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [B64Display].
|
||||
fn fmt_b64<const F: usize>(&self) -> B64DisplayHelper<F>;
|
||||
}
|
||||
|
||||
@@ -31,6 +54,11 @@ impl<T: AsRef<[u8]>> B64Display for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode a base64-encoded value
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [b64_encode].
|
||||
pub fn b64_decode(input: &[u8], output: &mut [u8]) -> anyhow::Result<()> {
|
||||
let mut reader = B64Reader::<Base64>::new(input).map_err(|e| anyhow::anyhow!(e))?;
|
||||
match reader.decode(output) {
|
||||
@@ -49,6 +77,23 @@ pub fn b64_decode(input: &[u8], output: &mut [u8]) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a value as base64.
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::b64::{b64_encode, b64_decode};
|
||||
///
|
||||
/// let bytes = b"Hello World";
|
||||
///
|
||||
/// let mut encoder_buffer = [0u8; 64];
|
||||
/// let encoded = b64_encode(bytes, &mut encoder_buffer)?;
|
||||
///
|
||||
/// let mut bytes_decoded = [0u8; 11];
|
||||
/// b64_decode(encoded.as_bytes(), &mut bytes_decoded);
|
||||
/// assert_eq!(bytes, &bytes_decoded);
|
||||
///
|
||||
/// Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
///
|
||||
pub fn b64_encode<'o>(input: &[u8], output: &'o mut [u8]) -> anyhow::Result<&'o str> {
|
||||
let mut writer = B64Writer::<Base64>::new(output).map_err(|e| anyhow::anyhow!(e))?;
|
||||
writer.encode(input).map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
@@ -1,33 +1,163 @@
|
||||
//! Lazy construction of values
|
||||
|
||||
use crate::{
|
||||
functional::ApplyExt,
|
||||
mem::{SwapWithDefaultExt, SwapWithExt},
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Errors returned by [ConstructionSite::erect]
|
||||
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
|
||||
pub enum ConstructionSiteErectError<E> {
|
||||
/// Attempted to erect an empty construction site
|
||||
#[error("Construction site is void")]
|
||||
IsVoid,
|
||||
/// Attempted to erect a construction that is already standing
|
||||
#[error("Construction is already built")]
|
||||
AlreadyBuilt,
|
||||
/// Other error
|
||||
#[error("Other construction site error {0:?}")]
|
||||
Other(#[from] E),
|
||||
}
|
||||
|
||||
/// A type that can build some other type
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::Build;
|
||||
/// use anyhow::{Context, Result};
|
||||
///
|
||||
/// #[derive(Eq, PartialEq, Debug)]
|
||||
/// struct Person {
|
||||
/// pub fav_pokemon: String,
|
||||
/// pub fav_number: u8,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default, Clone)]
|
||||
/// struct PersonBuilder {
|
||||
/// pub fav_pokemon: Option<String>,
|
||||
/// pub fav_number: Option<u8>,
|
||||
/// }
|
||||
///
|
||||
/// impl Build<Person> for &PersonBuilder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<Person, Self::Error> {
|
||||
/// let fav_pokemon = self.fav_pokemon.clone().context("Missing fav pokemon")?;
|
||||
/// let fav_number = self.fav_number.context("Missing fav number")?;
|
||||
/// Ok(Person {
|
||||
/// fav_pokemon,
|
||||
/// fav_number,
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut person_builder = PersonBuilder::default();
|
||||
/// assert!(person_builder.build().is_err());
|
||||
///
|
||||
/// person_builder.fav_pokemon = Some("Krabby".to_owned());
|
||||
/// person_builder.fav_number = Some(0);
|
||||
/// assert_eq!(
|
||||
/// person_builder.build().unwrap(),
|
||||
/// Person {
|
||||
/// fav_pokemon: "Krabby".to_owned(),
|
||||
/// fav_number: 0
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
pub trait Build<T>: Sized {
|
||||
/// Error returned by the builder
|
||||
type Error;
|
||||
/// Build the type
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self].
|
||||
fn build(self) -> Result<T, Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A type that can be incrementally built from a type that can [Build] it
|
||||
///
|
||||
/// This is similar to an option, where [Self::Void] is [std::Option::None],
|
||||
/// [Self::Product] is [std::Option::Some], except that there is a third
|
||||
/// intermediate state [Self::Builder] that represents a Some/Product value
|
||||
/// in the process of being made.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::borrow::Borrow;
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
/// use anyhow::{Context, Result};
|
||||
///
|
||||
/// #[derive(Eq, PartialEq, Debug)]
|
||||
/// struct Person {
|
||||
/// pub fav_pokemon: String,
|
||||
/// pub fav_number: u8,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Eq, PartialEq, Default, Clone, Debug)]
|
||||
/// struct PersonBuilder {
|
||||
/// pub fav_pokemon: Option<String>,
|
||||
/// pub fav_number: Option<u8>,
|
||||
/// }
|
||||
///
|
||||
/// impl Build<Person> for &PersonBuilder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<Person, Self::Error> {
|
||||
/// let fav_pokemon = self.fav_pokemon.clone().context("Missing fav pokemon")?;
|
||||
/// let fav_number = self.fav_number.context("Missing fav number")?;
|
||||
/// Ok(Person {
|
||||
/// fav_pokemon,
|
||||
/// fav_number,
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl Build<Person> for PersonBuilder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<Person, Self::Error> {
|
||||
/// self.borrow().build()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Allocate the construction site
|
||||
/// let mut site = ConstructionSite::void();
|
||||
///
|
||||
/// // Start construction
|
||||
/// site = ConstructionSite::Builder(PersonBuilder::default());
|
||||
///
|
||||
/// // Use the builder to build the value
|
||||
/// site.builder_mut().unwrap().fav_pokemon = Some("Krabby".to_owned());
|
||||
/// site.builder_mut().unwrap().fav_number = Some(0);
|
||||
///
|
||||
/// // Use `erect` to call Build::build
|
||||
/// site.erect();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// site,
|
||||
/// ConstructionSite::Product(Person {
|
||||
/// fav_pokemon: "Krabby".to_owned(),
|
||||
/// fav_number: 0
|
||||
/// }),
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum ConstructionSite<Builder, T>
|
||||
where
|
||||
Builder: Build<T>,
|
||||
{
|
||||
/// The site is empty
|
||||
Void,
|
||||
/// The site is being built
|
||||
Builder(Builder),
|
||||
/// The site has been built and is now finished
|
||||
Product(T),
|
||||
}
|
||||
|
||||
/// Initializes the construction site as [ConstructionSite::Void]
|
||||
impl<Builder, T> Default for ConstructionSite<Builder, T>
|
||||
where
|
||||
Builder: Build<T>,
|
||||
@@ -41,22 +171,189 @@ impl<Builder, T> ConstructionSite<Builder, T>
|
||||
where
|
||||
Builder: Build<T>,
|
||||
{
|
||||
/// Initializes the construction site as [ConstructionSite::Void]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self].
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ConstructionSite::<Builder, House>::void(),
|
||||
/// ConstructionSite::Void,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn void() -> Self {
|
||||
Self::Void
|
||||
}
|
||||
|
||||
/// Initialize the construction site from its builder
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ConstructionSite::<Builder, House>::new(Builder),
|
||||
/// ConstructionSite::Builder(Builder),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(builder: Builder) -> Self {
|
||||
Self::Builder(builder)
|
||||
}
|
||||
|
||||
/// Initialize the construction site from its product
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ConstructionSite::<Builder, House>::from_product(House),
|
||||
/// ConstructionSite::Product(House),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn from_product(value: T) -> Self {
|
||||
Self::Product(value)
|
||||
}
|
||||
|
||||
/// Extract the construction site and replace it with [Self::Void]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut a = ConstructionSite::<Builder, House>::from_product(House);
|
||||
/// let a_backup = a.clone();
|
||||
///
|
||||
/// let b = a.take();
|
||||
/// assert_eq!(a, ConstructionSite::void());
|
||||
/// assert_eq!(b, ConstructionSite::Product(House));
|
||||
/// ```
|
||||
pub fn take(&mut self) -> Self {
|
||||
self.swap_with_default()
|
||||
}
|
||||
|
||||
/// Apply the given function to Self, temporarily converting
|
||||
/// the mutable reference into an owned value.
|
||||
///
|
||||
/// This is useful if you have some function that needs to modify
|
||||
/// the construction site as an owned value but all you have is a reference.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House(u32);
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder(u32);
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House(self.0))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, PartialEq, Eq)]
|
||||
/// enum FancyMatchState {
|
||||
/// New,
|
||||
/// Built,
|
||||
/// Increment,
|
||||
/// };
|
||||
///
|
||||
/// fn fancy_match(site: &mut ConstructionSite<Builder, House>, def: u32) -> FancyMatchState {
|
||||
/// site.modify_taken_with_return(|site| {
|
||||
/// use ConstructionSite as C;
|
||||
/// use FancyMatchState as F;
|
||||
/// let (prod, state) = match site {
|
||||
/// C::Void => (House(def), F::New),
|
||||
/// C::Builder(b) => (b.build().unwrap(), F::Built),
|
||||
/// C::Product(House(v)) => (House(v + 1), F::Increment),
|
||||
/// };
|
||||
/// let prod = ConstructionSite::from_product(prod);
|
||||
/// (prod, state)
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// let mut a = ConstructionSite::void();
|
||||
/// let r = fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(42)));
|
||||
/// assert_eq!(r, FancyMatchState::New);
|
||||
///
|
||||
/// let mut a = ConstructionSite::new(Builder(13));
|
||||
/// let r = fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(13)));
|
||||
/// assert_eq!(r, FancyMatchState::Built);
|
||||
///
|
||||
/// let r = fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(14)));
|
||||
/// assert_eq!(r, FancyMatchState::Increment);
|
||||
/// ```
|
||||
pub fn modify_taken_with_return<R, F>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(Self) -> (Self, R),
|
||||
@@ -66,6 +363,53 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
/// Apply the given function to Self, temporarily converting
|
||||
/// the mutable reference into an owned value.
|
||||
///
|
||||
/// This is useful if you have some function that needs to modify
|
||||
/// the construction site as an owned value but all you have is a reference.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House(u32);
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder(u32);
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House(self.0))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn fancy_match(site: &mut ConstructionSite<Builder, House>, def: u32) {
|
||||
/// site.modify_taken(|site| {
|
||||
/// use ConstructionSite as C;
|
||||
/// let prod = match site {
|
||||
/// C::Void => House(def),
|
||||
/// C::Builder(b) => b.build().unwrap(),
|
||||
/// C::Product(House(v)) => House(v + 1),
|
||||
/// };
|
||||
/// ConstructionSite::from_product(prod)
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// let mut a = ConstructionSite::void();
|
||||
/// fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(42)));
|
||||
///
|
||||
/// let mut a = ConstructionSite::new(Builder(13));
|
||||
/// fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(13)));
|
||||
///
|
||||
/// fancy_match(&mut a, 42);
|
||||
/// assert_eq!(a, ConstructionSite::Product(House(14)));
|
||||
/// ```
|
||||
pub fn modify_taken<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(Self) -> Self,
|
||||
@@ -73,6 +417,42 @@ where
|
||||
self.take().apply(f).swap_with_mut(self)
|
||||
}
|
||||
|
||||
/// If this constructions site contains [Self::Builder], call the inner [Build]'s [Build::build]
|
||||
/// and have the construction site contain a product.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self].
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build, ConstructionSiteErectError};
|
||||
/// use std::convert::Infallible;
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = Infallible;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let mut a = ConstructionSite::<Builder, House>::void();
|
||||
/// assert_eq!(a.erect(), Err(ConstructionSiteErectError::IsVoid));
|
||||
/// assert_eq!(a, ConstructionSite::void());
|
||||
///
|
||||
/// let mut a = ConstructionSite::<Builder, House>::from_product(House);
|
||||
/// assert_eq!(a.erect(), Err(ConstructionSiteErectError::AlreadyBuilt));
|
||||
/// assert_eq!(a, ConstructionSite::from_product(House));
|
||||
///
|
||||
/// let mut a = ConstructionSite::<Builder, House>::new(Builder);
|
||||
/// a.erect().unwrap();
|
||||
/// assert_eq!(a, ConstructionSite::from_product(House));
|
||||
/// ```
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn erect(&mut self) -> Result<(), ConstructionSiteErectError<Builder::Error>> {
|
||||
self.modify_taken_with_return(|site| {
|
||||
@@ -98,6 +478,31 @@ where
|
||||
/// Returns `true` if the construction site is [`Void`].
|
||||
///
|
||||
/// [`Void`]: ConstructionSite::Void
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// type Site = ConstructionSite<Builder, House>;
|
||||
///
|
||||
/// assert_eq!(Site::Void.is_void(), true);
|
||||
/// assert_eq!(Site::Builder(Builder).is_void(), false);
|
||||
/// assert_eq!(Site::Product(House).is_void(), false);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn is_void(&self) -> bool {
|
||||
matches!(self, Self::Void)
|
||||
@@ -106,19 +511,95 @@ where
|
||||
/// Returns `true` if the construction site is [`InProgress`].
|
||||
///
|
||||
/// [`InProgress`]: ConstructionSite::InProgress
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// type Site = ConstructionSite<Builder, House>;
|
||||
///
|
||||
/// assert_eq!(Site::Void.in_progress(), false);
|
||||
/// assert_eq!(Site::Builder(Builder).in_progress(), true);
|
||||
/// assert_eq!(Site::Product(House).in_progress(), false);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn in_progess(&self) -> bool {
|
||||
pub fn in_progress(&self) -> bool {
|
||||
matches!(self, Self::Builder(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the construction site is [`Done`].
|
||||
///
|
||||
/// [`Done`]: ConstructionSite::Done
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// type Site = ConstructionSite<Builder, House>;
|
||||
///
|
||||
/// assert_eq!(Site::Void.is_available(), false);
|
||||
/// assert_eq!(Site::Builder(Builder).is_available(), false);
|
||||
/// assert_eq!(Site::Product(House).is_available(), true);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn is_available(&self) -> bool {
|
||||
matches!(self, Self::Product(..))
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Builder]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_util::build::{ConstructionSite, Build};
|
||||
///
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct House;
|
||||
/// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
/// struct Builder;
|
||||
///
|
||||
/// impl Build<House> for Builder {
|
||||
/// type Error = anyhow::Error;
|
||||
///
|
||||
/// fn build(self) -> Result<House, Self::Error> {
|
||||
/// Ok(House)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// type Site = ConstructionSite<Builder, House>;
|
||||
///
|
||||
/// assert_eq!(Site::Void.into_builder(), None);
|
||||
/// assert_eq!(Site::Builder(Builder).into_builder(), Some(Builder));
|
||||
/// assert_eq!(Site::Product(House).into_builder(), None);
|
||||
/// ```
|
||||
pub fn into_builder(self) -> Option<Builder> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
@@ -127,6 +608,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Builder] as a reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// See [Self::into_builder].
|
||||
pub fn builder_ref(&self) -> Option<&Builder> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
@@ -135,6 +621,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Builder] as a mutable reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Similar to [Self::into_builder].
|
||||
pub fn builder_mut(&mut self) -> Option<&mut Builder> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
@@ -143,6 +634,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Product]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Similar to [Self::into_builder].
|
||||
pub fn into_product(self) -> Option<T> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
@@ -151,6 +647,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Product] as a reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Similar to [Self::into_builder].
|
||||
pub fn product_ref(&self) -> Option<&T> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
@@ -159,6 +660,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of [Self::Product] as a mutable reference
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Similar to [Self::into_builder].
|
||||
pub fn product_mut(&mut self) -> Option<&mut T> {
|
||||
use ConstructionSite as S;
|
||||
match self {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
pub mod b64;
|
||||
|
||||
Reference in New Issue
Block a user