mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 21:34:37 +03:00
Compare commits
151 Commits
dev/karo/m
...
systemd-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3bebb9b9 | ||
|
|
9948169127 | ||
|
|
eced56bd70 | ||
|
|
df1e195b5d | ||
|
|
e1e280c4c5 | ||
|
|
06cd178977 | ||
|
|
e2c46f1ff0 | ||
|
|
c8b804b39d | ||
|
|
e56798b04c | ||
|
|
b76d18e3c8 | ||
|
|
a9792c3143 | ||
|
|
cb2c1c12ee | ||
|
|
08514d69e5 | ||
|
|
baf2d68070 | ||
|
|
cc7f7a4b4d | ||
|
|
5b701631b5 | ||
|
|
402158b706 | ||
|
|
e95636bf70 | ||
|
|
744e2bcf3e | ||
|
|
8c82ca18fb | ||
|
|
208e79c3a7 | ||
|
|
6ee023c9e9 | ||
|
|
6f75d34934 | ||
|
|
6b364a35dc | ||
|
|
2b6d10f0aa | ||
|
|
cb380b89d1 | ||
|
|
f703933e7f | ||
|
|
d02a5d2eb7 | ||
|
|
c7273e6f88 | ||
|
|
85eca49a5b | ||
|
|
9943f1336b | ||
|
|
bb2a0732cc | ||
|
|
1275b992a0 | ||
|
|
196767964f | ||
|
|
d4e9770fe6 | ||
|
|
8e2f6991d1 | ||
|
|
af0db88939 | ||
|
|
6601742903 | ||
|
|
9436281350 | ||
|
|
f3399907b9 | ||
|
|
0cea8c5eff | ||
|
|
5b3f4da23e | ||
|
|
c13badb697 | ||
|
|
cc7757a0db | ||
|
|
d267916445 | ||
|
|
03bc89a582 | ||
|
|
19b31bcdf0 | ||
|
|
939d216027 | ||
|
|
05fbaff2dc | ||
|
|
1d1c0e9da7 | ||
|
|
e19b724673 | ||
|
|
f879ad5020 | ||
|
|
29e7087cb5 | ||
|
|
637a08d222 | ||
|
|
6416c247f4 | ||
|
|
4b3b7e41e4 | ||
|
|
325fb915f0 | ||
|
|
43cb0c09c5 | ||
|
|
0836a2eb28 | ||
|
|
ca7df013d5 | ||
|
|
1209d68718 | ||
|
|
8806494899 | ||
|
|
582d27351a | ||
|
|
61136d79eb | ||
|
|
71bd406201 | ||
|
|
ce63cf534a | ||
|
|
d3ff19bdb9 | ||
|
|
3b6d0822d6 | ||
|
|
533afea129 | ||
|
|
da5b281b96 | ||
|
|
b9e873e534 | ||
|
|
a3b339b180 | ||
|
|
b4347c1382 | ||
|
|
0745019e10 | ||
|
|
2369006342 | ||
|
|
0fa6176d06 | ||
|
|
22bdeaf8f1 | ||
|
|
5731272844 | ||
|
|
bc7cef9de0 | ||
|
|
4cdcc35c3e | ||
|
|
a8f1292cbf | ||
|
|
ae5c5ed2b4 | ||
|
|
c483452a6a | ||
|
|
4ce331d299 | ||
|
|
d81eb7e2ed | ||
|
|
61043500ba | ||
|
|
9c4752559d | ||
|
|
6aec7acdb8 | ||
|
|
337cc1b4b4 | ||
|
|
387a266a49 | ||
|
|
179970b905 | ||
|
|
8b769e04c1 | ||
|
|
810bdf5519 | ||
|
|
d3a666bea0 | ||
|
|
2b8f780584 | ||
|
|
6aea3c0c1f | ||
|
|
e4fdfcae08 | ||
|
|
48e629fff7 | ||
|
|
6321bb36fc | ||
|
|
2f9ff487ba | ||
|
|
c0c06cd1dc | ||
|
|
e9772effa6 | ||
|
|
cf68f15674 | ||
|
|
dd5d45cdc9 | ||
|
|
17a6aed8a6 | ||
|
|
3f9926e353 | ||
|
|
f4ab2ac891 | ||
|
|
de51c1005f | ||
|
|
1e2cd589b1 | ||
|
|
02bc485d97 | ||
|
|
3ae52b9824 | ||
|
|
cbf361206b | ||
|
|
398da99df2 | ||
|
|
acfbb67abe | ||
|
|
c407b8b006 | ||
|
|
bc7213d8c0 | ||
|
|
69e68aad2c | ||
|
|
9b07f5803b | ||
|
|
5ce572b739 | ||
|
|
d9f8fa0092 | ||
|
|
a5208795f6 | ||
|
|
0959148305 | ||
|
|
f2bc3a8b64 | ||
|
|
06529df2c0 | ||
|
|
128c77f77a | ||
|
|
501cc9bb05 | ||
|
|
9ad5277a90 | ||
|
|
0cbcaeaf98 | ||
|
|
687ef3f6f8 | ||
|
|
b0706354d3 | ||
|
|
c1e86daec8 | ||
|
|
18a286e688 | ||
|
|
cb92313391 | ||
|
|
5cd30b4c13 | ||
|
|
76d8d38744 | ||
|
|
f63f0bbc2e | ||
|
|
4a449e6502 | ||
|
|
1e6d2df004 | ||
|
|
3fa9aadda2 | ||
|
|
0c79a4ce95 | ||
|
|
036960b5b1 | ||
|
|
e7258849cb | ||
|
|
8c88f68990 | ||
|
|
cf20536576 | ||
|
|
72e18e3ec2 | ||
|
|
6040156a0e | ||
|
|
d3b318b413 | ||
|
|
3a49345138 | ||
|
|
4ec7813259 | ||
|
|
db31da14d3 | ||
|
|
4c20efc8a8 |
14
.ci/boot_race/a.toml
Normal file
14
.ci/boot_race/a.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public_key = "rp-a-public-key"
|
||||||
|
secret_key = "rp-a-secret-key"
|
||||||
|
listen = ["127.0.0.1:9999"]
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "rp-b-public-key"
|
||||||
|
endpoint = "127.0.0.1:9998"
|
||||||
|
key_out = "rp-b-key-out.txt"
|
||||||
14
.ci/boot_race/b.toml
Normal file
14
.ci/boot_race/b.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public_key = "rp-b-public-key"
|
||||||
|
secret_key = "rp-b-secret-key"
|
||||||
|
listen = ["127.0.0.1:9998"]
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
listen_path = []
|
||||||
|
listen_fd = []
|
||||||
|
stream_fd = []
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "rp-a-public-key"
|
||||||
|
endpoint = "127.0.0.1:9999"
|
||||||
|
key_out = "rp-a-key-out.txt"
|
||||||
48
.ci/boot_race/run.sh
Normal file
48
.ci/boot_race/run.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
iterations="$1"
|
||||||
|
sleep_time="$2"
|
||||||
|
config_a="$3"
|
||||||
|
config_b="$4"
|
||||||
|
|
||||||
|
PWD="$(pwd)"
|
||||||
|
EXEC="$PWD/target/release/rosenpass"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -ne "$iterations" ]; do
|
||||||
|
echo "=> Iteration $i"
|
||||||
|
|
||||||
|
# flush the PSK files
|
||||||
|
echo "A" >rp-a-key-out.txt
|
||||||
|
echo "B" >rp-b-key-out.txt
|
||||||
|
|
||||||
|
# start the two instances
|
||||||
|
echo "Starting instance A"
|
||||||
|
"$EXEC" exchange-config "$config_a" &
|
||||||
|
PID_A=$!
|
||||||
|
sleep "$sleep_time"
|
||||||
|
echo "Starting instance B"
|
||||||
|
"$EXEC" exchange-config "$config_b" &
|
||||||
|
PID_B=$!
|
||||||
|
|
||||||
|
# give the key exchange some time to complete
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# kill the instances
|
||||||
|
kill $PID_A
|
||||||
|
kill $PID_B
|
||||||
|
|
||||||
|
# compare the keys
|
||||||
|
if cmp -s rp-a-key-out.txt rp-b-key-out.txt; then
|
||||||
|
echo "Keys match"
|
||||||
|
else
|
||||||
|
echo "::warning title=Key Exchange Race Condition::The key exchange resulted in different keys. Delay was ${sleep_time}s."
|
||||||
|
# TODO: set this to 1 when the race condition is fixed
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# give the instances some time to shut down
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|||||||
4
.github/workflows/doc-upload.yml
vendored
4
.github/workflows/doc-upload.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Clone rosenpass-website repository
|
- name: Clone rosenpass-website repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: rosenpass/rosenpass-website
|
repository: rosenpass/rosenpass-website
|
||||||
ref: main
|
ref: main
|
||||||
|
|||||||
205
.github/workflows/nix.yaml
vendored
205
.github/workflows/nix.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
i686-linux---default:
|
i686-linux---default:
|
||||||
name: Build i686-linux.default
|
name: Build i686-linux.default
|
||||||
@@ -14,11 +19,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -30,11 +35,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -47,11 +52,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- i686-linux---rosenpass
|
- i686-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -62,11 +67,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -79,11 +84,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-darwin---rosenpass
|
- x86_64-darwin---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -98,11 +103,11 @@ jobs:
|
|||||||
- x86_64-darwin---rp
|
- x86_64-darwin---rp
|
||||||
- x86_64-darwin---rosenpass-oci-image
|
- x86_64-darwin---rosenpass-oci-image
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -114,11 +119,11 @@ jobs:
|
|||||||
- macos-13
|
- macos-13
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -130,11 +135,11 @@ jobs:
|
|||||||
- macos-13
|
- macos-13
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -147,11 +152,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-darwin---rosenpass
|
- x86_64-darwin---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -162,11 +167,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- macos-13
|
- macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -179,11 +184,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -196,11 +201,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---proverif-patched
|
- x86_64-linux---proverif-patched
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -212,11 +217,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -231,51 +236,51 @@ jobs:
|
|||||||
- x86_64-linux---rosenpass-static-oci-image
|
- x86_64-linux---rosenpass-static-oci-image
|
||||||
- x86_64-linux---rp-static
|
- x86_64-linux---rp-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
run: nix build .#packages.x86_64-linux.release-package --print-build-logs
|
||||||
aarch64-linux---release-package:
|
# aarch64-linux---release-package:
|
||||||
name: Build aarch64-linux.release-package
|
# name: Build aarch64-linux.release-package
|
||||||
runs-on:
|
# runs-on:
|
||||||
- ubuntu-latest
|
# - ubuntu-latest
|
||||||
needs:
|
# needs:
|
||||||
- aarch64-linux---rosenpass-oci-image
|
# - aarch64-linux---rosenpass-oci-image
|
||||||
- aarch64-linux---rosenpass
|
# - aarch64-linux---rosenpass
|
||||||
- aarch64-linux---rp
|
# - aarch64-linux---rp
|
||||||
steps:
|
# steps:
|
||||||
- run: |
|
# - run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
# DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
# sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
# - uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
# - uses: cachix/install-nix-action@v30
|
||||||
with:
|
# with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
# nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
# extra_nix_config: |
|
||||||
system = aarch64-linux
|
# system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
# - uses: cachix/cachix-action@v15
|
||||||
with:
|
# with:
|
||||||
name: rosenpass
|
# name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build
|
# - name: Build
|
||||||
run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
# run: nix build .#packages.aarch64-linux.release-package --print-build-logs
|
||||||
x86_64-linux---rosenpass:
|
x86_64-linux---rosenpass:
|
||||||
name: Build x86_64-linux.rosenpass
|
name: Build x86_64-linux.rosenpass
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -290,13 +295,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -311,13 +316,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -330,11 +335,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass
|
- x86_64-linux---rosenpass
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -350,13 +355,13 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
DEBIAN_FRONTEND=noninteractive
|
DEBIAN_FRONTEND=noninteractive
|
||||||
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
sudo apt-get update -q -y && sudo apt-get install -q -y qemu-system-aarch64 qemu-efi binfmt-support qemu-user-static
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system = aarch64-linux
|
system = aarch64-linux
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -368,11 +373,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -384,11 +389,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -401,11 +406,11 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- x86_64-linux---rosenpass-static
|
- x86_64-linux---rosenpass-static
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -417,11 +422,11 @@ jobs:
|
|||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
needs: []
|
needs: []
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -432,11 +437,11 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -447,11 +452,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -460,7 +465,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
run: nix build .#packages.x86_64-linux.whitepaper --print-build-logs
|
||||||
- name: Deploy PDF artifacts
|
- name: Deploy PDF artifacts
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: result/
|
publish_dir: result/
|
||||||
|
|||||||
50
.github/workflows/qc.yaml
vendored
50
.github/workflows/qc.yaml
vendored
@@ -4,6 +4,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -12,8 +16,8 @@ jobs:
|
|||||||
prettier:
|
prettier:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actionsx/prettier@v2
|
- uses: actionsx/prettier@v3
|
||||||
with:
|
with:
|
||||||
args: --check .
|
args: --check .
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ jobs:
|
|||||||
name: Shellcheck
|
name: Shellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: ludeeus/action-shellcheck@master
|
||||||
|
|
||||||
@@ -29,15 +33,15 @@ jobs:
|
|||||||
name: Rust Format
|
name: Rust Format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Run Rust Formatting Script
|
- name: Run Rust Formatting Script
|
||||||
run: bash format_rust_code.sh --mode check
|
run: bash format_rust_code.sh --mode check
|
||||||
|
|
||||||
cargo-bench:
|
cargo-bench:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -57,16 +61,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install mandoc
|
- name: Install mandoc
|
||||||
run: sudo apt-get install -y mandoc
|
run: sudo apt-get install -y mandoc
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Check rosenpass.1
|
|
||||||
run: doc/check.sh doc/rosenpass.1
|
|
||||||
- name: Check rp.1
|
- name: Check rp.1
|
||||||
run: doc/check.sh doc/rp.1
|
run: doc/check.sh doc/rp.1
|
||||||
|
|
||||||
cargo-audit:
|
cargo-audit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rs/audit-check@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -74,8 +76,8 @@ jobs:
|
|||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -93,8 +95,8 @@ jobs:
|
|||||||
cargo-doc:
|
cargo-doc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -117,8 +119,8 @@ jobs:
|
|||||||
# - ubuntu is x86-64
|
# - ubuntu is x86-64
|
||||||
# - macos-13 is also x86-64 architecture
|
# - macos-13 is also x86-64 architecture
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -136,8 +138,8 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -146,10 +148,10 @@ jobs:
|
|||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
- uses: cachix/install-nix-action@v21
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
@@ -158,8 +160,8 @@ jobs:
|
|||||||
cargo-fuzz:
|
cargo-fuzz:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/bin/
|
~/.cargo/bin/
|
||||||
@@ -191,7 +193,7 @@ jobs:
|
|||||||
codecov:
|
codecov:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: rustup component add llvm-tools-preview
|
- run: rustup component add llvm-tools-preview
|
||||||
- run: |
|
- run: |
|
||||||
cargo install cargo-llvm-cov || true
|
cargo install cargo-llvm-cov || true
|
||||||
@@ -204,7 +206,7 @@ jobs:
|
|||||||
#- run: cargo install cargo-tarpaulin
|
#- run: cargo install cargo-tarpaulin
|
||||||
#- run: cargo tarpaulin --out Xml
|
#- run: cargo tarpaulin --out Xml
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
files: ./coverage.lcov
|
files: ./coverage.lcov
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|||||||
20
.github/workflows/regressions.yml
vendored
20
.github/workflows/regressions.yml
vendored
@@ -1,9 +1,13 @@
|
|||||||
name: QC
|
name: Regressions
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
checks: write
|
checks: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -12,10 +16,22 @@ jobs:
|
|||||||
multi-peer:
|
multi-peer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: cargo build --bin rosenpass --release
|
- run: cargo build --bin rosenpass --release
|
||||||
- run: python misc/generate_configs.py
|
- run: python misc/generate_configs.py
|
||||||
- run: chmod +x .ci/run-regression.sh
|
- run: chmod +x .ci/run-regression.sh
|
||||||
- run: .ci/run-regression.sh 100 20
|
- run: .ci/run-regression.sh 100 20
|
||||||
- run: |
|
- run: |
|
||||||
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
[ $(ls -1 output/ate/out | wc -l) -eq 100 ]
|
||||||
|
|
||||||
|
boot-race:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cargo build --bin rosenpass --release
|
||||||
|
- run: chmod +x .ci/boot_race/run.sh
|
||||||
|
- run: cargo run --release --bin rosenpass gen-keys .ci/boot_race/a.toml
|
||||||
|
- run: cargo run --release --bin rosenpass gen-keys .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 2 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 1 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
|
- run: .ci/boot_race/run.sh 5 0 .ci/boot_race/a.toml .ci/boot_race/b.toml
|
||||||
|
|||||||
24
.github/workflows/release.yaml
vendored
24
.github/workflows/release.yaml
vendored
@@ -11,18 +11,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -32,18 +32,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- macos-13
|
- macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
@@ -53,18 +53,18 @@ jobs:
|
|||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@v30
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-unstable
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
- uses: cachix/cachix-action@v12
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
name: rosenpass
|
name: rosenpass
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: nix build .#release-package --print-build-logs
|
run: nix build .#release-package --print-build-logs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
draft: ${{ contains(github.ref_name, 'rc') }}
|
draft: ${{ contains(github.ref_name, 'rc') }}
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.direnv/
|
.direnv/
|
||||||
|
flake.lock
|
||||||
papers/whitepaper.md
|
papers/whitepaper.md
|
||||||
target/
|
|
||||||
src/usage.md
|
src/usage.md
|
||||||
|
target/
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ If any other issue occurs
|
|||||||
1. Make sure you locally checked out the head of the main branch
|
1. Make sure you locally checked out the head of the main branch
|
||||||
- `git stash --include-untracked && git checkout main && git pull`
|
- `git stash --include-untracked && git checkout main && git pull`
|
||||||
2. Make sure all tests pass
|
2. Make sure all tests pass
|
||||||
- `cargo test`
|
- `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)
|
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
|
- 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`, ...)
|
4. Pick the kind of release that you want to make (`major`, `minor`, `patch`, `rc`, ...)
|
||||||
|
|||||||
669
Cargo.lock
generated
669
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
44
Cargo.toml
44
Cargo.toml
@@ -32,57 +32,61 @@ rosenpass-secret-memory = { path = "secret-memory" }
|
|||||||
rosenpass-oqs = { path = "oqs" }
|
rosenpass-oqs = { path = "oqs" }
|
||||||
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
rosenpass-wireguard-broker = { path = "wireguard-broker" }
|
||||||
doc-comment = "0.3.3"
|
doc-comment = "0.3.3"
|
||||||
base64ct = {version = "1.6.0", default-features=false}
|
base64ct = { version = "1.6.0", default-features = false }
|
||||||
zeroize = "1.8.1"
|
zeroize = "1.8.1"
|
||||||
memoffset = "0.9.1"
|
memoffset = "0.9.1"
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.69"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
env_logger = "0.10.2"
|
env_logger = "0.10.2"
|
||||||
toml = "0.7.8"
|
toml = "0.7.8"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
allocator-api2 = "0.2.14"
|
allocator-api2 = "0.2.14"
|
||||||
memsec = { git="https://github.com/rosenpass/memsec.git" ,rev="aceb9baee8aec6844125bd6612f92e9a281373df", features = [ "alloc_ext", ] }
|
memsec = { git = "https://github.com/rosenpass/memsec.git", rev = "aceb9baee8aec6844125bd6612f92e9a281373df", features = [
|
||||||
|
"alloc_ext",
|
||||||
|
] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
typenum = "1.17.0"
|
typenum = "1.17.0"
|
||||||
log = { version = "0.4.22" }
|
log = { version = "0.4.22" }
|
||||||
clap = { version = "4.5.16", features = ["derive"] }
|
clap = { version = "4.5.22", features = ["derive"] }
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
clap_mangen = "0.2.24"
|
||||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
clap_complete = "4.5.38"
|
||||||
anyhow = { version = "1.0.86", features = ["backtrace", "std"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
mio = { version = "1.0.2", features = ["net", "os-poll"] }
|
arbitrary = { version = "1.4.1", features = ["derive"] }
|
||||||
|
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
|
||||||
|
mio = { version = "1.0.3", features = ["net", "os-poll"] }
|
||||||
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
oqs-sys = { version = "0.9.1", default-features = false, features = [
|
||||||
'classic_mceliece',
|
'classic_mceliece',
|
||||||
'kyber',
|
'kyber',
|
||||||
] }
|
] }
|
||||||
blake2 = "0.10.6"
|
blake2 = "0.10.6"
|
||||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
chacha20poly1305 = { version = "0.10.1", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"heapless",
|
"heapless",
|
||||||
] }
|
] }
|
||||||
zerocopy = { version = "0.7.35", features = ["derive"] }
|
zerocopy = { version = "0.7.35", features = ["derive"] }
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
derive_builder = "0.20.0"
|
derive_builder = "0.20.1"
|
||||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] }
|
||||||
postcard= {version = "1.0.9", features = ["alloc"]}
|
postcard = { version = "1.1.1", features = ["alloc"] }
|
||||||
libcrux = { version = "0.0.2-pre.2" }
|
libcrux = { version = "0.0.2-pre.2" }
|
||||||
hex-literal = { version = "0.4.1" }
|
hex-literal = { version = "0.4.1" }
|
||||||
hex = { version = "0.4.3" }
|
hex = { version = "0.4.3" }
|
||||||
heck = { version = "0.5.0" }
|
heck = { version = "0.5.0" }
|
||||||
libc = { version = "0.2" }
|
libc = { version = "0.2" }
|
||||||
uds = { git = "https://github.com/rosenpass/uds" }
|
uds = { git = "https://github.com/rosenpass/uds" }
|
||||||
|
|
||||||
#Dev dependencies
|
#Dev dependencies
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.2.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
stacker = "0.1.15"
|
stacker = "0.1.17"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
test_bin = "0.4.0"
|
test_bin = "0.4.0"
|
||||||
criterion = "0.4.0"
|
criterion = "0.4.0"
|
||||||
allocator-api2-tests = "0.2.15"
|
allocator-api2-tests = "0.2.15"
|
||||||
procspawn = {version = "1.0.1", features= ["test-support"]}
|
procspawn = { version = "1.0.1", features = ["test-support"] }
|
||||||
|
|
||||||
|
|
||||||
#Broker dependencies (might need cleanup or changes)
|
#Broker dependencies (might need cleanup or changes)
|
||||||
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
wireguard-uapi = { version = "3.0.0", features = ["xplatform"] }
|
||||||
command-fds = "0.2.3"
|
command-fds = "0.2.3"
|
||||||
rustix = { version = "0.38.27", features = ["net", "fs"] }
|
rustix = { version = "0.38.41", features = ["net", "fs"] }
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ static_assertions = { workspace = true }
|
|||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
chacha20poly1305 = { workspace = true }
|
chacha20poly1305 = { workspace = true }
|
||||||
blake2 = { workspace = true }
|
blake2 = { workspace = true }
|
||||||
libcrux = { workspace = true, optional = true }
|
libcrux = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
//! Constant-time comparison
|
||||||
|
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
|
||||||
/// Little endian memcmp version of quinier/memsec
|
/// Little endian memcmp version of quinier/memsec
|
||||||
/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30
|
/// 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)]
|
#[inline(never)]
|
||||||
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
||||||
let mut res = 0;
|
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
|
((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
|
/// compares two slices of memory content and returns an integer indicating the relationship between
|
||||||
/// the slices
|
/// the slices
|
||||||
///
|
///
|
||||||
@@ -32,6 +50,28 @@ pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 {
|
|||||||
/// ## Tests
|
/// ## Tests
|
||||||
/// For discussion on how to ensure the constant-time execution of this function, see
|
/// For discussion on how to ensure the constant-time execution of this function, see
|
||||||
/// <https://github.com/rosenpass/rosenpass/issues/232>
|
/// <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]
|
#[inline]
|
||||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||||
assert!(a.len() == b.len());
|
assert!(a.len() == b.len());
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Incrementing numbers
|
||||||
|
|
||||||
use core::hint::black_box;
|
use core::hint::black_box;
|
||||||
|
|
||||||
/// Interpret the given slice as a little-endian unsigned integer
|
/// 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
|
//! constant-time implementations of some primitives
|
||||||
//!
|
//!
|
||||||
//! Rosenpass internal library providing basic constant-time operations.
|
//! 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
|
/// compares two sclices of memory content and returns whether they are equal
|
||||||
///
|
///
|
||||||
/// ## Leaks
|
/// ## Leaks
|
||||||
@@ -7,6 +9,18 @@
|
|||||||
///
|
///
|
||||||
/// The execution time of the function grows approx. linear with the length of the input. This is
|
/// The execution time of the function grows approx. linear with the length of the input. This is
|
||||||
/// considered safe.
|
/// 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]
|
#[inline]
|
||||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||||
a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) }
|
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 core::hint::black_box;
|
||||||
use rosenpass_to::{with_destination, To};
|
use rosenpass_to::{with_destination, To};
|
||||||
|
|
||||||
|
|||||||
114
doc/rosenpass.1
114
doc/rosenpass.1
@@ -1,114 +0,0 @@
|
|||||||
.Dd $Mdocdate$
|
|
||||||
.Dt ROSENPASS 1
|
|
||||||
.Os
|
|
||||||
.Sh NAME
|
|
||||||
.Nm rosenpass
|
|
||||||
.Nd builds post-quantum-secure VPNs
|
|
||||||
.Sh SYNOPSIS
|
|
||||||
.Nm
|
|
||||||
.Op COMMAND
|
|
||||||
.Op Ar OPTIONS ...
|
|
||||||
.Op Ar ARGS ...
|
|
||||||
.Sh DESCRIPTION
|
|
||||||
.Nm
|
|
||||||
performs cryptographic key exchanges that are secure against quantum-computers
|
|
||||||
and then outputs the keys.
|
|
||||||
These keys can then be passed to various services, such as wireguard or other
|
|
||||||
vpn services, as pre-shared-keys to achieve security against attackers with
|
|
||||||
quantum computers.
|
|
||||||
.Pp
|
|
||||||
This is a research project and quantum computers are not thought to become
|
|
||||||
practical in fewer than ten years.
|
|
||||||
If you are not specifically tasked with developing post-quantum secure systems,
|
|
||||||
you probably do not need this tool.
|
|
||||||
.Ss COMMANDS
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar gen-keys --secret-key <file-path> --public-key <file-path>
|
|
||||||
Generate a keypair to use in the exchange command later.
|
|
||||||
Send the public-key file to your communication partner and keep the private-key
|
|
||||||
file secret!
|
|
||||||
.It Ar exchange private-key <file-path> public-key <file-path> [ OPTIONS ] PEERS
|
|
||||||
Start a process to exchange keys with the specified peers.
|
|
||||||
You should specify at least one peer.
|
|
||||||
.Pp
|
|
||||||
Its
|
|
||||||
.Ar OPTIONS
|
|
||||||
are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar listen <ip>[:<port>]
|
|
||||||
Instructs
|
|
||||||
.Nm
|
|
||||||
to listen on the specified interface and port.
|
|
||||||
By default,
|
|
||||||
.Nm
|
|
||||||
will listen on all interfaces and select a random port.
|
|
||||||
.It Ar verbose
|
|
||||||
Extra logging.
|
|
||||||
.El
|
|
||||||
.El
|
|
||||||
.Ss PEER
|
|
||||||
Each
|
|
||||||
.Ar PEER
|
|
||||||
is defined as follows:
|
|
||||||
.Qq peer public-key <file-path> [endpoint <ip>[:<port>]] [preshared-key <file-path>] [outfile <file-path>] [wireguard <dev> <peer> <extra_params>]
|
|
||||||
.Pp
|
|
||||||
Providing a
|
|
||||||
.Ar PEER
|
|
||||||
instructs
|
|
||||||
.Nm
|
|
||||||
to exchange keys with the given peer and write the resulting PSK into the given
|
|
||||||
output file.
|
|
||||||
You must either specify the outfile or wireguard output option.
|
|
||||||
.Pp
|
|
||||||
The parameters of
|
|
||||||
.Ar PEER
|
|
||||||
are as follows:
|
|
||||||
.Bl -tag -width Ds
|
|
||||||
.It Ar endpoint <ip>[:<port>]
|
|
||||||
Specifies the address where the peer can be reached.
|
|
||||||
This will be automatically updated after the first successful key exchange with
|
|
||||||
the peer.
|
|
||||||
If this is unspecified, the peer must initiate the connection.
|
|
||||||
.It Ar preshared-key <file-path>
|
|
||||||
You may specify a pre-shared key which will be mixed into the final secret.
|
|
||||||
.It Ar outfile <file-path>
|
|
||||||
You may specify a file to write the exchanged keys to.
|
|
||||||
If this option is specified,
|
|
||||||
.Nm
|
|
||||||
will write a notification to standard out every time the key is updated.
|
|
||||||
.It Ar wireguard <dev> <peer> <extra_params>
|
|
||||||
This allows you to directly specify a wireguard peer to deploy the
|
|
||||||
pre-shared-key to.
|
|
||||||
You may specify extra parameters you would pass to
|
|
||||||
.Qq wg set
|
|
||||||
besides the preshared-key parameter which is used by
|
|
||||||
.Nm .
|
|
||||||
This makes it possible to add peers entirely from
|
|
||||||
.Nm .
|
|
||||||
.El
|
|
||||||
.Sh EXIT STATUS
|
|
||||||
.Ex -std
|
|
||||||
.Sh SEE ALSO
|
|
||||||
.Xr rp 1 ,
|
|
||||||
.Xr wg 1
|
|
||||||
.Rs
|
|
||||||
.%A Karolin Varner
|
|
||||||
.%A Benjamin Lipp
|
|
||||||
.%A Wanja Zaeske
|
|
||||||
.%A Lisa Schmidt
|
|
||||||
.%D 2023
|
|
||||||
.%T Rosenpass
|
|
||||||
.%U https://rosenpass.eu/whitepaper.pdf
|
|
||||||
.Re
|
|
||||||
.Sh STANDARDS
|
|
||||||
This tool is the reference implementation of the Rosenpass protocol, as
|
|
||||||
specified within the whitepaper referenced above.
|
|
||||||
.Sh AUTHORS
|
|
||||||
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
|
||||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
|
||||||
.Pp
|
|
||||||
This manual page was written by
|
|
||||||
.An Clara Engler
|
|
||||||
.Sh BUGS
|
|
||||||
The bugs are tracked at
|
|
||||||
.Lk https://github.com/rosenpass/rosenpass/issues .
|
|
||||||
49
flake.lock
generated
49
flake.lock
generated
@@ -2,15 +2,17 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"fenix": {
|
"fenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": ["nixpkgs"],
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712298178,
|
"lastModified": 1728282832,
|
||||||
"narHash": "sha256-590fpCPXYAkaAeBz/V91GX4/KGzPObdYtqsTWzT6AhI=",
|
"narHash": "sha256-I7AbcwGggf+CHqpyd/9PiAjpIBGTGx5woYHqtwxaV7I=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "569b5b5781395da08e7064e825953c548c26af76",
|
"rev": "1ec71be1f4b8f3105c5d38da339cb061fefc43f4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -24,11 +26,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710146030,
|
"lastModified": 1726560853,
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -37,36 +39,18 @@
|
|||||||
"type": "github"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712168706,
|
"lastModified": 1728193676,
|
||||||
"narHash": "sha256-XP24tOobf6GGElMd0ux90FEBalUtw6NkBSVh/RlA6ik=",
|
"narHash": "sha256-PbDWAIjKJdlVg+qQRhzdSor04bAPApDqIv2DofTyynk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1487bdea619e4a7a53a4590c475deabb5a9d1bfb",
|
"rev": "ecbc1ca8ffd6aea8372ad16be9ebbb39889e55b6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-23.11",
|
"ref": "nixos-24.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -75,18 +59,17 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"naersk": "naersk",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712156296,
|
"lastModified": 1728249780,
|
||||||
"narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=",
|
"narHash": "sha256-J269DvCI5dzBmPrXhAAtj566qt0b22TJtF3TIK+tMsI=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "8e581ac348e223488622f4d3003cb2bd412bf27e",
|
"rev": "2b750da1a1a2c1d2c70896108d7096089842d877",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
413
flake.nix
413
flake.nix
@@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
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
|
# for rust nightly with llvm-tools-preview
|
||||||
fenix.url = "github:nix-community/fenix";
|
fenix.url = "github:nix-community/fenix";
|
||||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -15,6 +11,15 @@
|
|||||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||||
nixpkgs.lib.foldl (a: b: nixpkgs.lib.recursiveUpdate a b) { } [
|
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 ###
|
### Actual Rosenpass Package and Docker Container Images ###
|
||||||
#
|
#
|
||||||
@@ -30,310 +35,39 @@
|
|||||||
]
|
]
|
||||||
(system:
|
(system:
|
||||||
let
|
let
|
||||||
scoped = (scope: scope.result);
|
|
||||||
lib = nixpkgs.lib;
|
|
||||||
|
|
||||||
# normal nixpkgs
|
# normal nixpkgs
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
|
||||||
|
|
||||||
# parsed Cargo.toml
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
overlays = [ self.overlays.default ];
|
||||||
|
|
||||||
# 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" ];
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
rec {
|
{
|
||||||
packages = rec {
|
packages = {
|
||||||
default = rosenpass;
|
default = pkgs.rosenpass;
|
||||||
rosenpass = rosenpassDerivation pkgs;
|
rosenpass = pkgs.rosenpass;
|
||||||
rp = rpDerivation pkgs;
|
rosenpass-oci-image = pkgs.rosenpass-oci-image;
|
||||||
rosenpass-oci-image = rosenpassOCI "rosenpass";
|
rp = pkgs.rp;
|
||||||
|
|
||||||
# derivation for the release
|
release-package = pkgs.release-package;
|
||||||
release-package =
|
|
||||||
let
|
# for good measure, we also offer to cross compile to Linux on Arm
|
||||||
version = cargoToml.package.version;
|
aarch64-linux-rosenpass-static =
|
||||||
package =
|
pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rosenpass;
|
||||||
if pkgs.hostPlatform.isLinux then
|
aarch64-linux-rp-static = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.rp;
|
||||||
packages.rosenpass-static
|
}
|
||||||
else packages.rosenpass;
|
//
|
||||||
rp =
|
# We only offer static builds for linux, as this is not supported on OS X
|
||||||
if pkgs.hostPlatform.isLinux then
|
(nixpkgs.lib.attrsets.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
packages.rp-static
|
rosenpass-static = pkgs.pkgsStatic.rosenpass;
|
||||||
else packages.rp;
|
rosenpass-static-oci-image = pkgs.pkgsStatic.rosenpass-oci-image;
|
||||||
oci-image =
|
rp-static = pkgs.pkgsStatic.rp;
|
||||||
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 { });
|
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
### Linux specifics ###
|
### Linux specifics ###
|
||||||
#
|
#
|
||||||
@@ -341,92 +75,53 @@
|
|||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
|
||||||
|
# apply our own overlay, overriding/inserting our packages as defined in ./pkgs
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
};
|
};
|
||||||
packages = self.packages.${system};
|
|
||||||
in
|
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 ###
|
### Proof and Proof Tools ###
|
||||||
#
|
#
|
||||||
packages.proverif-patched = pkgs.proverif.overrideAttrs (old: {
|
packages.proverif-patched = pkgs.proverif-patched;
|
||||||
postInstall = ''
|
packages.proof-proverif = pkgs.proof-proverif;
|
||||||
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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
### Devshells ###
|
### Devshells ###
|
||||||
#
|
#
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
inherit (packages.proof-proverif) CRYPTOVERIF_LIB;
|
inherit (pkgs.proof-proverif) CRYPTOVERIF_LIB;
|
||||||
inputsFrom = [ packages.default ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
inputs.fenix.packages.${system}.complete.toolchain
|
|
||||||
cmake # override the fakecmake from the main step above
|
|
||||||
cargo-release
|
cargo-release
|
||||||
clippy
|
clippy
|
||||||
|
rustfmt
|
||||||
nodePackages.prettier
|
nodePackages.prettier
|
||||||
nushell # for the .ci/gen-workflow-files.nu script
|
nushell # for the .ci/gen-workflow-files.nu script
|
||||||
packages.proverif-patched
|
proverif-patched
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
devShells.coverage = pkgs.mkShell {
|
devShells.coverage = pkgs.mkShell {
|
||||||
inputsFrom = [ packages.default ];
|
inputsFrom = [ pkgs.rosenpass ];
|
||||||
nativeBuildInputs = with pkgs; [ inputs.fenix.packages.${system}.complete.toolchain cargo-llvm-cov ];
|
nativeBuildInputs = [
|
||||||
|
inputs.fenix.packages.${system}.complete.toolchain
|
||||||
|
pkgs.cargo-llvm-cov
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
checks = {
|
checks = {
|
||||||
|
systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix;
|
||||||
|
systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix;
|
||||||
|
|
||||||
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
||||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ rosenpass-cipher-traits = { workspace = true }
|
|||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
oqs-sys = { workspace = true }
|
oqs-sys = { workspace = true }
|
||||||
paste = { 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 {
|
macro_rules! oqs_kem {
|
||||||
($name:ident) => { ::paste::paste!{
|
($name:ident) => { ::paste::paste!{
|
||||||
|
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
|
||||||
mod [< $name:snake >] {
|
mod [< $name:snake >] {
|
||||||
use rosenpass_cipher_traits::Kem;
|
use rosenpass_cipher_traits::Kem;
|
||||||
use rosenpass_util::result::Guaranteed;
|
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 >] {}
|
pub enum [< $name:camel >] {}
|
||||||
|
|
||||||
/// # Panic & Safety
|
/// # 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 {
|
macro_rules! oqs_call {
|
||||||
($name:path, $($args:expr),*) => {{
|
($name:path, $($args:expr),*) => {{
|
||||||
use oqs_sys::common::OQS_STATUS::*;
|
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 lib/systemd \
|
||||||
|
-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" ];
|
||||||
|
}
|
||||||
87
pkgs/rosenpass.nix
Normal file
87
pkgs/rosenpass.nix
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{ 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"
|
||||||
|
"service"
|
||||||
|
"target"
|
||||||
|
"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";
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
install systemd/rosenpass@.service $out/lib/systemd/system
|
||||||
|
install systemd/rp@.service $out/lib/systemd/system
|
||||||
|
install systemd/rosenpass.target $out/lib/systemd/system
|
||||||
|
'';
|
||||||
|
|
||||||
|
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]
|
[package]
|
||||||
name = "rosenpass"
|
name = "rosenpass"
|
||||||
version = "0.2.1"
|
version = "0.3.0-dev"
|
||||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@@ -47,18 +47,20 @@ env_logger = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
clap_complete = { workspace = true }
|
||||||
|
clap_mangen = { workspace = true }
|
||||||
mio = { workspace = true }
|
mio = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
zerocopy = { workspace = true }
|
zerocopy = { workspace = true }
|
||||||
home = { workspace = true }
|
home = { workspace = true }
|
||||||
derive_builder = {workspace = true}
|
derive_builder = { workspace = true }
|
||||||
rosenpass-wireguard-broker = {workspace = true}
|
rosenpass-wireguard-broker = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
hex-literal = { workspace = true, optional = true }
|
hex-literal = { workspace = true, optional = true }
|
||||||
hex = { workspace = true, optional = true }
|
hex = { workspace = true, optional = true }
|
||||||
heck = { workspace = true, optional = true }
|
heck = { workspace = true, optional = true }
|
||||||
command-fds = { workspace = true, optional = true }
|
command-fds = { workspace = true, optional = true }
|
||||||
rustix = { workspace = true }
|
rustix = { workspace = true, optional = true }
|
||||||
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
uds = { workspace = true, optional = true, features = ["mio_1xx"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
@@ -68,15 +70,22 @@ anyhow = { workspace = true }
|
|||||||
criterion = { workspace = true }
|
criterion = { workspace = true }
|
||||||
test_bin = { workspace = true }
|
test_bin = { workspace = true }
|
||||||
stacker = { workspace = true }
|
stacker = { workspace = true }
|
||||||
serial_test = {workspace = true}
|
serial_test = { workspace = true }
|
||||||
procspawn = {workspace = true}
|
procspawn = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
rustix = {workspace = true}
|
rustix = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["experiment_api"]
|
default = []
|
||||||
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]
|
||||||
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"]
|
||||||
experiment_api = ["hex-literal", "uds", "command-fds", "rosenpass-util/experiment_file_descriptor_passing", "rosenpass-wireguard-broker/experiment_api"]
|
experiment_api = [
|
||||||
internal_testing = []
|
"hex-literal",
|
||||||
|
"uds",
|
||||||
|
"command-fds",
|
||||||
|
"rustix",
|
||||||
|
"rosenpass-util/experiment_file_descriptor_passing",
|
||||||
|
"rosenpass-wireguard-broker/experiment_api",
|
||||||
|
]
|
||||||
|
internal_testing = []
|
||||||
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
internal_bin_gen_ipc_msg_types = ["hex", "heck"]
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
use anyhow::bail;
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::env;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
/// Invokes a troff compiler to compile a manual page
|
|
||||||
fn render_man(compiler: &str, man: &str) -> Result<String> {
|
|
||||||
let out = Command::new(compiler).args(["-Tascii", man]).output()?;
|
|
||||||
if !out.status.success() {
|
|
||||||
bail!("{} returned an error", compiler);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(String::from_utf8(out.stdout)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the manual page
|
|
||||||
fn generate_man() -> String {
|
|
||||||
// This function is purposely stupid and redundant
|
|
||||||
|
|
||||||
let man = render_man("mandoc", "./doc/rosenpass.1");
|
|
||||||
if let Ok(man) = man {
|
|
||||||
return man;
|
|
||||||
}
|
|
||||||
|
|
||||||
let man = render_man("groff", "./doc/rosenpass.1");
|
|
||||||
if let Ok(man) = man {
|
|
||||||
return man;
|
|
||||||
}
|
|
||||||
|
|
||||||
"Cannot render manual page. Please visit https://rosenpass.eu/docs/manuals/\n".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn man() {
|
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
||||||
let man = generate_man();
|
|
||||||
let path = out_dir.join("rosenpass.1.ascii");
|
|
||||||
|
|
||||||
let mut file = File::create(&path).unwrap();
|
|
||||||
file.write_all(man.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
println!("cargo:rustc-env=ROSENPASS_MAN={}", path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// For now, rerun the build script on every time, as the build script
|
|
||||||
// is not very expensive right now.
|
|
||||||
println!("cargo:rerun-if-changed=./");
|
|
||||||
man();
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Note: This is business logic; tested through the integration tests in
|
||||||
|
// rosenpass/tests/
|
||||||
|
|
||||||
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -20,37 +23,80 @@ use crate::{
|
|||||||
|
|
||||||
use super::{supply_keypair_response_status, Server as ApiServer};
|
use super::{supply_keypair_response_status, Server as ApiServer};
|
||||||
|
|
||||||
|
/// Stores the state of the API handler.
|
||||||
|
///
|
||||||
|
/// This is used in the context [ApiHandlerContext]; [ApiHandlerContext] exposes both
|
||||||
|
/// the [AppServer] and the API handler state.
|
||||||
|
///
|
||||||
|
/// [ApiHandlerContext] is what actually contains the API handler functions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ApiHandler {
|
pub struct ApiHandler {
|
||||||
_dummy: (),
|
_dummy: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiHandler {
|
impl ApiHandler {
|
||||||
|
/// Construct an [Self]
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { _dummy: () }
|
Self { _dummy: () }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The implementation of the API requires both access to its own state [ApiHandler] and to the
|
||||||
|
/// [AppServer] the API is supposed to operate on.
|
||||||
|
///
|
||||||
|
/// This trait provides both; it implements a pattern to allow for multiple - **potentially
|
||||||
|
/// overlapping** mutable references to be passed to the API handler functions.
|
||||||
|
///
|
||||||
|
/// This relatively complex scheme is chosen to appease the borrow checker: We want flexibility
|
||||||
|
/// with regard to where the [ApiHandler] is stored and we need a mutable reference to
|
||||||
|
/// [ApiHandler]. We also need a mutable reference to [AppServer]. Achieving this by using the
|
||||||
|
/// direct method would be impossible because the [ApiHandler] is actually stored somewhere inside
|
||||||
|
/// [AppServer]. The borrow checker does not allow this.
|
||||||
|
///
|
||||||
|
/// What we have instead is – in practice – a reference to [AppServer] and a function (as part of
|
||||||
|
/// the trait) that extracts an [ApiHandler] reference from [AppServer], which is allowed by the
|
||||||
|
/// borrow checker. A benefit of the use of a trait here is that we could, if desired, also store
|
||||||
|
/// the [ApiHandler] outside [AppServer]. It really depends on the trait.
|
||||||
pub trait ApiHandlerContext {
|
pub trait ApiHandlerContext {
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
fn api_handler(&self) -> &ApiHandler;
|
fn api_handler(&self) -> &ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
fn app_server(&self) -> &AppServer;
|
fn app_server(&self) -> &AppServer;
|
||||||
|
/// Retrieve the [ApiHandler]
|
||||||
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
fn api_handler_mut(&mut self) -> &mut ApiHandler;
|
||||||
|
/// Retrieve the [AppServer]
|
||||||
fn app_server_mut(&mut self) -> &mut AppServer;
|
fn app_server_mut(&mut self) -> &mut AppServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the Error raised by [ApiServer::supply_keypair]; it contains both
|
||||||
|
/// the underlying error message as well as the status value
|
||||||
|
/// returned by the API.
|
||||||
|
///
|
||||||
|
/// [ApiServer::supply_keypair] generally constructs a [Self] by using one of the
|
||||||
|
/// utility functions [SupplyKeypairErrorExt].
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[error("Error in SupplyKeypair")]
|
#[error("Error in SupplyKeypair")]
|
||||||
struct SupplyKeypairError {
|
struct SupplyKeypairError {
|
||||||
|
/// The status code communicated via the Rosenpass API
|
||||||
status: u128,
|
status: u128,
|
||||||
|
/// The underlying error that caused the Rosenpass API level Error
|
||||||
#[source]
|
#[source]
|
||||||
cause: anyhow::Error,
|
cause: anyhow::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SupplyKeypairErrorExt<T> {
|
trait SupplyKeypairErrorExt<T> {
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// an arbitrary error code
|
||||||
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError>;
|
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::INTERNAL_ERROR] error code
|
||||||
fn einternal(self) -> Result<T, SupplyKeypairError>;
|
fn einternal(self) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED] error code
|
||||||
fn ealready_supplied(self) -> Result<T, SupplyKeypairError>;
|
fn ealready_supplied(self) -> Result<T, SupplyKeypairError>;
|
||||||
|
/// Imbue any Error (that can be represented as [anyhow::Error]) with
|
||||||
|
/// the [supply_keypair_response_status::INVALID_REQUEST] error code
|
||||||
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
|
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,8 +140,10 @@ impl Message for SupplyKeypairRequest {
|
|||||||
pub mod supply_keypair_response_status {
|
pub mod supply_keypair_response_status {
|
||||||
pub const OK: u128 = 0;
|
pub const OK: u128 = 0;
|
||||||
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
|
||||||
|
// TODO: This is not actually part of the API. Remove.
|
||||||
pub const INTERNAL_ERROR: u128 = 2;
|
pub const INTERNAL_ERROR: u128 = 2;
|
||||||
pub const INVALID_REQUEST: u128 = 3;
|
pub const INVALID_REQUEST: u128 = 3;
|
||||||
|
/// TODO: Deprectaed, remove
|
||||||
pub const IO_ERROR: u128 = 4;
|
pub const IO_ERROR: u128 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ use std::{collections::VecDeque, os::fd::OwnedFd};
|
|||||||
use zerocopy::{ByteSlice, ByteSliceMut};
|
use zerocopy::{ByteSlice, ByteSliceMut};
|
||||||
|
|
||||||
pub trait Server {
|
pub trait Server {
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
|
||||||
|
///
|
||||||
|
/// It merely takes a buffer and returns that same buffer.
|
||||||
fn ping(
|
fn ping(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &PingRequest,
|
req: &PingRequest,
|
||||||
@@ -10,6 +13,47 @@ pub trait Server {
|
|||||||
res: &mut PingResponse,
|
res: &mut PingResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Supply the cryptographic server keypair through file descriptor passing in the API
|
||||||
|
///
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::SupplyKeypair] API message.
|
||||||
|
///
|
||||||
|
/// # File descriptors
|
||||||
|
///
|
||||||
|
/// 1. The secret key (size must match exactly); the file descriptor must be backed by either
|
||||||
|
/// of
|
||||||
|
/// - file-system file
|
||||||
|
/// - [memfd](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// - [memfd_secret](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// 2. The public key (size must match exactly); the file descriptor must be backed by either
|
||||||
|
/// of
|
||||||
|
/// - file-system file
|
||||||
|
/// - [memfd](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
/// - [memfd_secret](https://man.archlinux.org/man/memfd.2.en)
|
||||||
|
///
|
||||||
|
/// # API Return Status
|
||||||
|
///
|
||||||
|
/// 1. [crate::api::supply_keypair_response_status::OK] - Indicates success
|
||||||
|
/// 2. [crate::api::supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED] – The endpoint was used but
|
||||||
|
/// the server already has server keys
|
||||||
|
/// 3. [crate::api::supply_keypair_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||||
|
/// - Missing file descriptors for public key
|
||||||
|
/// - File descriptors contain data of invalid length
|
||||||
|
/// - Invalid file descriptor type
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// At startup, if no server keys are specified in the rosenpass configuration, and if the API
|
||||||
|
/// is enabled, the Rosenpass process waits for server keys to be supplied to the API. Before
|
||||||
|
/// then, any messages for the rosenpass cryptographic protocol are ignored and dropped – all
|
||||||
|
/// cryptographic operations require access to the server keys.
|
||||||
|
///
|
||||||
|
/// Both private and public keys are specified through file descriptors and both are read from
|
||||||
|
/// their respective file descriptors into process memory. A file descriptor based transport is
|
||||||
|
/// used because of the excessive size of Classic McEliece public keys (100kb and up).
|
||||||
|
///
|
||||||
|
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
|
||||||
|
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
|
||||||
|
/// backed file descriptor if the server keys are not backed by a file system file.
|
||||||
fn supply_keypair(
|
fn supply_keypair(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::SupplyKeypairRequest,
|
req: &super::SupplyKeypairRequest,
|
||||||
@@ -17,6 +61,27 @@ pub trait Server {
|
|||||||
res: &mut super::SupplyKeypairResponse,
|
res: &mut super::SupplyKeypairResponse,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// Supply a new UDP listen socket through file descriptor passing via the API
|
||||||
|
///
|
||||||
|
/// This implements the handler for the [crate::api::RequestMsgType::AddListenSocket] API message.
|
||||||
|
///
|
||||||
|
/// # File descriptors
|
||||||
|
///
|
||||||
|
/// 1. The listen socket; must be backed by a UDP network listen socket
|
||||||
|
///
|
||||||
|
/// # API Return Status
|
||||||
|
///
|
||||||
|
/// 1. [crate::api::add_listen_socket_response_status::OK] - Indicates success
|
||||||
|
/// 2. [add_listen_socket_response_status::INVALID_REQUEST] – Malformed request; could be:
|
||||||
|
/// - Missing file descriptors for public key
|
||||||
|
/// - Invalid file descriptor type
|
||||||
|
/// 3. [crate::api::add_listen_socket_response_status::INTERNAL_ERROR] – Some other, non-fatal error
|
||||||
|
/// occured. Check the logs on log
|
||||||
|
///
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
|
||||||
|
/// cryptographic key exchanges via the Rosenpass protocol.
|
||||||
fn add_listen_socket(
|
fn add_listen_socket(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: &super::AddListenSocketRequest,
|
req: &super::AddListenSocketRequest,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ impl MioConnection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shoud_close(&self) -> bool {
|
pub fn should_close(&self) -> bool {
|
||||||
let exhausted = self
|
let exhausted = self
|
||||||
.buffers
|
.buffers
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -262,7 +262,7 @@ pub trait MioConnectionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn should_close(&self) -> bool {
|
fn should_close(&self) -> bool {
|
||||||
self.mio_connection().shoud_close()
|
self.mio_connection().should_close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ pub struct AppServerTest {
|
|||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum AppServerIoSource {
|
pub enum AppServerIoSource {
|
||||||
Socket(usize),
|
Socket(usize),
|
||||||
#[cfg(feature = "experiment_api")]
|
|
||||||
PskBroker(Public<BROKER_ID_BYTES>),
|
PskBroker(Public<BROKER_ID_BYTES>),
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
MioManager(crate::api::mio::MioManagerIoSource),
|
MioManager(crate::api::mio::MioManagerIoSource),
|
||||||
@@ -1209,15 +1208,12 @@ impl AppServer {
|
|||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
io_source: AppServerIoSource,
|
io_source: AppServerIoSource,
|
||||||
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
) -> anyhow::Result<Option<(usize, Endpoint)>> {
|
||||||
use crate::api::mio::MioManagerContext;
|
|
||||||
|
|
||||||
match io_source {
|
match io_source {
|
||||||
AppServerIoSource::Socket(idx) => self
|
AppServerIoSource::Socket(idx) => self
|
||||||
.try_recv_from_listen_socket(buf, idx)
|
.try_recv_from_listen_socket(buf, idx)
|
||||||
.substitute_for_ioerr_wouldblock(None)?
|
.substitute_for_ioerr_wouldblock(None)?
|
||||||
.ok(),
|
.ok(),
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
|
||||||
AppServerIoSource::PskBroker(key) => self
|
AppServerIoSource::PskBroker(key) => self
|
||||||
.brokers
|
.brokers
|
||||||
.store
|
.store
|
||||||
@@ -1227,9 +1223,13 @@ impl AppServer {
|
|||||||
.map(|_| None),
|
.map(|_| None),
|
||||||
|
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
AppServerIoSource::MioManager(mmio_src) => MioManagerFocus(self)
|
AppServerIoSource::MioManager(mmio_src) => {
|
||||||
.poll_particular(mmio_src)
|
use crate::api::mio::MioManagerContext;
|
||||||
.map(|_| None),
|
|
||||||
|
MioManagerFocus(self)
|
||||||
|
.poll_particular(mmio_src)
|
||||||
|
.map(|_| None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ use {
|
|||||||
rosenpass_util::fd::claim_fd,
|
rosenpass_util::fd::claim_fd,
|
||||||
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient,
|
||||||
rosenpass_wireguard_broker::WireguardBrokerMio,
|
rosenpass_wireguard_broker::WireguardBrokerMio,
|
||||||
rustix::fd::AsRawFd,
|
|
||||||
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
rustix::net::{socketpair, AddressFamily, SocketFlags, SocketType},
|
||||||
|
std::os::fd::AsRawFd,
|
||||||
std::os::unix::net,
|
std::os::unix::net,
|
||||||
std::process::Command,
|
std::process::Command,
|
||||||
std::thread,
|
std::thread,
|
||||||
@@ -41,17 +41,17 @@ pub enum BrokerInterface {
|
|||||||
|
|
||||||
/// struct holding all CLI arguments for `clap` crate to parse
|
/// struct holding all CLI arguments for `clap` crate to parse
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about)]
|
#[command(author, version, about, long_about, arg_required_else_help = true)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
/// lowest log level to show – log messages at higher levels will be omitted
|
/// Lowest log level to show
|
||||||
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
#[arg(long = "log-level", value_name = "LOG_LEVEL", group = "log-level")]
|
||||||
log_level: Option<log::LevelFilter>,
|
log_level: Option<log::LevelFilter>,
|
||||||
|
|
||||||
/// show verbose log output – sets log level to "debug"
|
/// Show verbose log output – sets log level to "debug"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
/// show no log output – sets log level to "error"
|
/// Show no log output – sets log level to "error"
|
||||||
#[arg(short, long, group = "log-level")]
|
#[arg(short, long, group = "log-level")]
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
|
||||||
@@ -59,28 +59,42 @@ pub struct CliArgs {
|
|||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
api: crate::api::cli::ApiCli,
|
api: crate::api::cli::ApiCli,
|
||||||
|
|
||||||
/// path of the wireguard_psk broker socket to connect to
|
/// Path of the `wireguard_psk` broker socket to connect to
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(long, group = "psk-broker-specs")]
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
psk_broker_path: Option<PathBuf>,
|
psk_broker_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// fd of the wireguard_spk broker socket to connect to
|
/// File descriptor of the `wireguard_psk` broker socket to connect to
|
||||||
///
|
///
|
||||||
/// when this command is called from another process, the other process can open and bind the
|
/// When this command is called from another process, the other process can
|
||||||
/// Unix socket for the psk broker connection to use themselves, passing it to this process --
|
/// open and bind the Unix socket for the PSK broker connection to use
|
||||||
/// in Rust this can be achieved using the
|
/// themselves, passing it to this process - in Rust this can be achieved
|
||||||
/// [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate
|
/// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/)
|
||||||
|
/// crate
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(long, group = "psk-broker-specs")]
|
#[arg(long, group = "psk-broker-specs")]
|
||||||
psk_broker_fd: Option<i32>,
|
psk_broker_fd: Option<i32>,
|
||||||
|
|
||||||
/// spawn a psk broker locally using a socket pair
|
/// Spawn a PSK broker locally using a socket pair
|
||||||
#[cfg(feature = "experiment_api")]
|
#[cfg(feature = "experiment_api")]
|
||||||
#[arg(short, long, group = "psk-broker-specs")]
|
#[arg(short, long, group = "psk-broker-specs")]
|
||||||
psk_broker_spawn: bool,
|
psk_broker_spawn: bool,
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: CliCommand,
|
pub command: Option<CliCommand>,
|
||||||
|
|
||||||
|
/// Generate man pages for the CLI
|
||||||
|
///
|
||||||
|
/// This option is used to generate man pages for Rosenpass in the specified
|
||||||
|
/// directory and exit.
|
||||||
|
#[clap(long, value_name = "out_dir")]
|
||||||
|
pub generate_manpage: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Generate completion file for a shell
|
||||||
|
///
|
||||||
|
/// This option is used to generate completion files for the specified shell
|
||||||
|
#[clap(long, value_name = "shell")]
|
||||||
|
pub print_completions: Option<clap_complete::Shell>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
@@ -135,20 +149,20 @@ impl CliArgs {
|
|||||||
/// represents a command specified via CLI
|
/// represents a command specified via CLI
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum CliCommand {
|
pub enum CliCommand {
|
||||||
/// Start Rosenpass in server mode and carry on with the key exchange
|
/// Start Rosenpass key exchanges based on a configuration file
|
||||||
///
|
///
|
||||||
/// This will parse the configuration file and perform the key exchange
|
/// This will parse the configuration file and perform key exchanges with
|
||||||
/// with the specified peers. If a peer's endpoint is specified, this
|
/// the specified peers. If a peer's endpoint is specified, this Rosenpass
|
||||||
/// Rosenpass instance will try to initiate a key exchange with the peer,
|
/// instance will try to initiate a key exchange with the peer; otherwise,
|
||||||
/// otherwise only initiation attempts from the peer will be responded to.
|
/// only initiation attempts from other peers will be responded to.
|
||||||
ExchangeConfig { config_file: PathBuf },
|
ExchangeConfig { config_file: PathBuf },
|
||||||
|
|
||||||
/// Start in daemon mode, performing key exchanges
|
/// Start Rosenpass key exchanges based on command line arguments
|
||||||
///
|
///
|
||||||
/// The configuration is read from the command line. The `peer` token
|
/// The configuration is read from the command line. The `peer` token always
|
||||||
/// always separates multiple peers, e. g. if the token `peer` appears
|
/// separates multiple peers, e.g., if the token `peer` appears in the
|
||||||
/// in the WIREGUARD_EXTRA_ARGS it is not put into the WireGuard arguments
|
/// WIREGUARD_EXTRA_ARGS, it is not put into the WireGuard arguments but
|
||||||
/// but instead a new peer is created.
|
/// instead a new peer is created.
|
||||||
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
||||||
* `Vec<String>`. They are only used to trick clap into displaying some
|
* `Vec<String>`. They are only used to trick clap into displaying some
|
||||||
* guidance on the CLI usage.
|
* guidance on the CLI usage.
|
||||||
@@ -177,7 +191,10 @@ pub enum CliCommand {
|
|||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate a demo config file
|
/// Generate a demo config file for Rosenpass
|
||||||
|
///
|
||||||
|
/// The generated config file will contain a single peer and all common
|
||||||
|
/// options.
|
||||||
GenConfig {
|
GenConfig {
|
||||||
config_file: PathBuf,
|
config_file: PathBuf,
|
||||||
|
|
||||||
@@ -186,19 +203,19 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Generate the keys mentioned in a configFile
|
/// Generate secret & public key for Rosenpass
|
||||||
///
|
///
|
||||||
/// Generates secret- & public-key to their destination. If a config file
|
/// Generates secret & public key to their destination. If a config file is
|
||||||
/// is provided then the key file destination is taken from there.
|
/// provided then the key file destination is taken from there, otherwise
|
||||||
/// Otherwise the
|
/// the destination is taken from the CLI arguments.
|
||||||
GenKeys {
|
GenKeys {
|
||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write public-key to
|
/// Where to write public key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
public_key: Option<PathBuf>,
|
public_key: Option<PathBuf>,
|
||||||
|
|
||||||
/// where to write secret-key to
|
/// Where to write secret key to
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
secret_key: Option<PathBuf>,
|
secret_key: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -207,25 +224,27 @@ pub enum CliCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Deprecated - use gen-keys instead
|
/// Validate a configuration file
|
||||||
|
///
|
||||||
|
/// This command will validate the configuration file and print any errors
|
||||||
|
/// it finds. If the configuration file is valid, it will print a success.
|
||||||
|
/// Defined secret & public keys are checked for existence and validity.
|
||||||
|
Validate { config_files: Vec<PathBuf> },
|
||||||
|
|
||||||
|
/// DEPRECATED - use the gen-keys command instead
|
||||||
#[allow(rustdoc::broken_intra_doc_links)]
|
#[allow(rustdoc::broken_intra_doc_links)]
|
||||||
#[allow(rustdoc::invalid_html_tags)]
|
#[allow(rustdoc::invalid_html_tags)]
|
||||||
|
#[command(hide = true)]
|
||||||
Keygen {
|
Keygen {
|
||||||
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
|
// NOTE yes, the legacy keygen argument initially really accepted
|
||||||
|
// "private-key", not "secret-key"!
|
||||||
/// public-key <PATH> private-key <PATH>
|
/// public-key <PATH> private-key <PATH>
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Validate a configuration
|
|
||||||
Validate { config_files: Vec<PathBuf> },
|
|
||||||
|
|
||||||
/// Show the rosenpass manpage
|
|
||||||
// TODO make this the default, but only after the manpage has been adjusted once the CLI stabilizes
|
|
||||||
Man,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
/// runs the command specified via CLI
|
/// Runs the command specified via CLI
|
||||||
///
|
///
|
||||||
/// ## TODO
|
/// ## TODO
|
||||||
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
|
||||||
@@ -236,26 +255,17 @@ impl CliArgs {
|
|||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
use CliCommand::*;
|
use CliCommand::*;
|
||||||
match &self.command {
|
match &self.command {
|
||||||
Man => {
|
Some(GenConfig { config_file, force }) => {
|
||||||
let man_cmd = std::process::Command::new("man")
|
|
||||||
.args(["1", "rosenpass"])
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if !(man_cmd.is_ok() && man_cmd.unwrap().success()) {
|
|
||||||
println!(include_str!(env!("ROSENPASS_MAN")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GenConfig { config_file, force } => {
|
|
||||||
ensure!(
|
ensure!(
|
||||||
*force || !config_file.exists(),
|
*force || !config_file.exists(),
|
||||||
"config file {config_file:?} already exists"
|
"config file {config_file:?} already exists"
|
||||||
);
|
);
|
||||||
|
|
||||||
config::Rosenpass::example_config().store(config_file)?;
|
std::fs::write(config_file, config::EXAMPLE_CONFIG)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated - use gen-keys instead
|
// Deprecated - use gen-keys instead
|
||||||
Keygen { args } => {
|
Some(Keygen { args }) => {
|
||||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||||
|
|
||||||
let mut public_key: Option<PathBuf> = None;
|
let mut public_key: Option<PathBuf> = None;
|
||||||
@@ -288,12 +298,12 @@ impl CliArgs {
|
|||||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
GenKeys {
|
Some(GenKeys {
|
||||||
config_file,
|
config_file,
|
||||||
public_key,
|
public_key,
|
||||||
secret_key,
|
secret_key,
|
||||||
force,
|
force,
|
||||||
} => {
|
}) => {
|
||||||
// figure out where the key file is specified, in the config file or directly as flag?
|
// figure out where the key file is specified, in the config file or directly as flag?
|
||||||
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||||
(Some(config_file), _, _) => {
|
(Some(config_file), _, _) => {
|
||||||
@@ -337,7 +347,7 @@ impl CliArgs {
|
|||||||
generate_and_save_keypair(skf, pkf)?;
|
generate_and_save_keypair(skf, pkf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExchangeConfig { config_file } => {
|
Some(ExchangeConfig { config_file }) => {
|
||||||
ensure!(
|
ensure!(
|
||||||
config_file.exists(),
|
config_file.exists(),
|
||||||
"config file '{config_file:?}' does not exist"
|
"config file '{config_file:?}' does not exist"
|
||||||
@@ -351,11 +361,11 @@ impl CliArgs {
|
|||||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Exchange {
|
Some(Exchange {
|
||||||
first_arg,
|
first_arg,
|
||||||
rest_of_args,
|
rest_of_args,
|
||||||
config_file,
|
config_file,
|
||||||
} => {
|
}) => {
|
||||||
let mut rest_of_args = rest_of_args.clone();
|
let mut rest_of_args = rest_of_args.clone();
|
||||||
rest_of_args.insert(0, first_arg.clone());
|
rest_of_args.insert(0, first_arg.clone());
|
||||||
let args = rest_of_args;
|
let args = rest_of_args;
|
||||||
@@ -372,20 +382,22 @@ impl CliArgs {
|
|||||||
Self::event_loop(config, broker_interface, test_helpers)?;
|
Self::event_loop(config, broker_interface, test_helpers)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate { config_files } => {
|
Some(Validate { config_files }) => {
|
||||||
for file in config_files {
|
for file in config_files {
|
||||||
match config::Rosenpass::load(file) {
|
match config::Rosenpass::load(file) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||||
match config.validate() {
|
match config.validate() {
|
||||||
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
Ok(_) => eprintln!("{file:?} has passed all logical checks"),
|
||||||
Err(_) => eprintln!("{file:?} contains logical errors"),
|
Err(err) => eprintln!("{file:?} contains logical errors: '{err}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&None => {} // calp print help if no command is given
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
//! ## TODO
|
//! ## TODO
|
||||||
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
|
||||||
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
|
||||||
|
use crate::protocol::{SPk, SSk};
|
||||||
|
use rosenpass_util::file::LoadValue;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
@@ -207,23 +208,33 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validate a configuration
|
/// Validate a configuration
|
||||||
///
|
|
||||||
/// ## TODO
|
|
||||||
/// - check that files do not just exist but are also readable
|
|
||||||
/// - warn if neither out_key nor exchange_command of a peer is defined (v.i.)
|
|
||||||
pub fn validate(&self) -> anyhow::Result<()> {
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
if let Some(ref keypair) = self.keypair {
|
if let Some(ref keypair) = self.keypair {
|
||||||
// check the public key file exists
|
// check the public key file exists
|
||||||
ensure!(
|
ensure!(
|
||||||
keypair.public_key.is_file(),
|
keypair.public_key.is_file(),
|
||||||
"could not find public-key file {:?}: no such file",
|
"could not find public-key file {:?}: no such file. Consider running `rosenpass gen-keys` to generate a new keypair.",
|
||||||
|
keypair.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&keypair.public_key).is_ok(),
|
||||||
|
"could not load public-key file {:?}: invalid key",
|
||||||
keypair.public_key
|
keypair.public_key
|
||||||
);
|
);
|
||||||
|
|
||||||
// check the secret-key file exists
|
// check the secret-key file exists
|
||||||
ensure!(
|
ensure!(
|
||||||
keypair.secret_key.is_file(),
|
keypair.secret_key.is_file(),
|
||||||
"could not find secret-key file {:?}: no such file",
|
"could not find secret-key file {:?}: no such file. Consider running `rosenpass gen-keys` to generate a new keypair.",
|
||||||
|
keypair.secret_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the secret-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SSk::load(&keypair.secret_key).is_ok(),
|
||||||
|
"could not load public-key file {:?}: invalid key",
|
||||||
keypair.secret_key
|
keypair.secret_key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -236,6 +247,13 @@ impl Rosenpass {
|
|||||||
peer.public_key
|
peer.public_key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// check peer's public-key file is a valid key
|
||||||
|
ensure!(
|
||||||
|
SPk::load(&peer.public_key).is_ok(),
|
||||||
|
"peer {i} public-key file {:?} is invalid",
|
||||||
|
peer.public_key
|
||||||
|
);
|
||||||
|
|
||||||
// check endpoint is usable
|
// check endpoint is usable
|
||||||
if let Some(addr) = peer.endpoint.as_ref() {
|
if let Some(addr) = peer.endpoint.as_ref() {
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -245,7 +263,22 @@ impl Rosenpass {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO warn if neither out_key nor exchange_command is defined
|
// check if `key_out` or `device` and `peer` are defined
|
||||||
|
if peer.key_out.is_none() {
|
||||||
|
if let Some(wg) = &peer.wg {
|
||||||
|
if wg.device.is_empty() || wg.peer.is_empty() {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ensure!(
|
||||||
|
false,
|
||||||
|
"peer {i} has neither `key_out` nor valid wireguard config defined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -491,38 +524,31 @@ impl Rosenpass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rosenpass {
|
|
||||||
/// Generate an example configuration
|
|
||||||
pub fn example_config() -> Self {
|
|
||||||
let peer = RosenpassPeer {
|
|
||||||
public_key: "/path/to/rp-peer-public-key".into(),
|
|
||||||
endpoint: Some("my-peer.test:9999".into()),
|
|
||||||
key_out: Some("/path/to/rp-key-out.txt".into()),
|
|
||||||
pre_shared_key: Some("additional pre shared key".into()),
|
|
||||||
wg: Some(WireGuard {
|
|
||||||
device: "wirgeguard device e.g. wg0".into(),
|
|
||||||
peer: "wireguard public key".into(),
|
|
||||||
extra_params: vec!["passed to".into(), "wg set".into()],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
keypair: Some(Keypair {
|
|
||||||
public_key: "/path/to/rp-public-key".into(),
|
|
||||||
secret_key: "/path/to/rp-secret-key".into(),
|
|
||||||
}),
|
|
||||||
peers: vec![peer],
|
|
||||||
..Self::new(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Verbosity {
|
impl Default for Verbosity {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Quiet
|
Self::Quiet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
|
||||||
|
secret_key = "/path/to/rp-secret-key"
|
||||||
|
listen = []
|
||||||
|
verbosity = "Verbose"
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
# Commented out fields are optional
|
||||||
|
public_key = "/path/to/rp-peer-public-key"
|
||||||
|
endpoint = "127.0.0.1:9998"
|
||||||
|
# pre_shared_key = "/path/to/preshared-key"
|
||||||
|
|
||||||
|
# Choose to store the key in a file via `key_out` or pass it to WireGuard by
|
||||||
|
# defining `device` and `peer`. You may choose to do both.
|
||||||
|
key_out = "/path/to/rp-key-out.txt" # path to store the key
|
||||||
|
# device = "wg0" # WireGuard interface
|
||||||
|
#peer = "RULdRAtUw7SFfVfGD..." # WireGuard public key
|
||||||
|
# extra_params = [] # passed to WireGuard `wg set`
|
||||||
|
"###;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,51 @@
|
|||||||
|
use clap::CommandFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap_mangen::roff::{roman, Roff};
|
||||||
use log::error;
|
use log::error;
|
||||||
use rosenpass::cli::CliArgs;
|
use rosenpass::cli::CliArgs;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
fn print_custom_man_section(section: &str, text: &str, file: &mut std::fs::File) {
|
||||||
|
let mut roff = Roff::default();
|
||||||
|
roff.control("SH", [section]);
|
||||||
|
roff.text([roman(text)]);
|
||||||
|
let _ = roff.to_writer(file);
|
||||||
|
}
|
||||||
|
|
||||||
/// Catches errors, prints them through the logger, then exits
|
/// Catches errors, prints them through the logger, then exits
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// parse CLI arguments
|
// parse CLI arguments
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
|
|
||||||
|
if let Some(shell) = args.print_completions {
|
||||||
|
let mut cmd = CliArgs::command();
|
||||||
|
clap_complete::generate(shell, &mut cmd, "rosenpass", &mut std::io::stdout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(out_dir) = args.generate_manpage {
|
||||||
|
std::fs::create_dir_all(&out_dir).expect("Failed to create man pages directory");
|
||||||
|
|
||||||
|
let cmd = CliArgs::command();
|
||||||
|
let man = clap_mangen::Man::new(cmd.clone());
|
||||||
|
let _ = clap_mangen::generate_to(cmd, &out_dir);
|
||||||
|
|
||||||
|
let file_path = out_dir.join("rosenpass.1");
|
||||||
|
let mut file = std::fs::File::create(file_path).expect("Failed to create man page file");
|
||||||
|
|
||||||
|
let _ = man.render_title(&mut file);
|
||||||
|
let _ = man.render_name_section(&mut file);
|
||||||
|
let _ = man.render_synopsis_section(&mut file);
|
||||||
|
let _ = man.render_subcommands_section(&mut file);
|
||||||
|
let _ = man.render_options_section(&mut file);
|
||||||
|
print_custom_man_section("EXIT STATUS", EXIT_STATUS_MAN, &mut file);
|
||||||
|
print_custom_man_section("SEE ALSO", SEE_ALSO_MAN, &mut file);
|
||||||
|
print_custom_man_section("STANDARDS", STANDARDS_MAN, &mut file);
|
||||||
|
print_custom_man_section("AUTHORS", AUTHORS_MAN, &mut file);
|
||||||
|
print_custom_man_section("BUGS", BUGS_MAN, &mut file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
use rosenpass_secret_memory as SM;
|
use rosenpass_secret_memory as SM;
|
||||||
#[cfg(feature = "experiment_memfd_secret")]
|
#[cfg(feature = "experiment_memfd_secret")]
|
||||||
@@ -43,3 +81,21 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static EXIT_STATUS_MAN: &str = r"
|
||||||
|
The rosenpass utility exits 0 on success, and >0 if an error occurs.";
|
||||||
|
|
||||||
|
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.";
|
||||||
|
|
||||||
|
static STANDARDS_MAN: &str = r"
|
||||||
|
This tool is the reference implementation of the Rosenpass protocol, as
|
||||||
|
specified within the whitepaper referenced above.";
|
||||||
|
|
||||||
|
static AUTHORS_MAN: &str = r"
|
||||||
|
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske, Marei
|
||||||
|
Peischl, Stephan Ajuvo, and Lisa Schmidt.";
|
||||||
|
|
||||||
|
static BUGS_MAN: &str = r"
|
||||||
|
The bugs are tracked at https://github.com/rosenpass/rosenpass/issues.";
|
||||||
|
|||||||
@@ -134,11 +134,10 @@ pub const PEER_COOKIE_VALUE_EPOCH: Timing = 120.0;
|
|||||||
// decryption for a second epoch
|
// decryption for a second epoch
|
||||||
pub const BISCUIT_EPOCH: Timing = 300.0;
|
pub const BISCUIT_EPOCH: Timing = 300.0;
|
||||||
|
|
||||||
// Retransmission pub constants; will retransmit for up to _ABORT ms; starting with a delay of
|
// Retransmission pub constants; will retransmit for up to _ABORT seconds;
|
||||||
// _DELAY_BEG ms and increasing the delay exponentially by a factor of
|
// starting with a delay of _DELAY_BEGIN seconds and increasing the delay
|
||||||
// _DELAY_GROWTH up to _DELAY_END. An additional jitter factor of ±_DELAY_JITTER
|
// exponentially by a factor of _DELAY_GROWTH up to _DELAY_END.
|
||||||
// is added.
|
// An additional jitter factor of ±_DELAY_JITTER is added.
|
||||||
pub const RETRANSMIT_ABORT: Timing = 120.0;
|
|
||||||
pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0;
|
pub const RETRANSMIT_DELAY_GROWTH: Timing = 2.0;
|
||||||
pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5;
|
pub const RETRANSMIT_DELAY_BEGIN: Timing = 0.5;
|
||||||
pub const RETRANSMIT_DELAY_END: Timing = 10.0;
|
pub const RETRANSMIT_DELAY_END: Timing = 10.0;
|
||||||
@@ -1473,7 +1472,7 @@ impl IniHsPtr {
|
|||||||
.min(ih.tx_count as f64),
|
.min(ih.tx_count as f64),
|
||||||
)
|
)
|
||||||
* RETRANSMIT_DELAY_JITTER
|
* RETRANSMIT_DELAY_JITTER
|
||||||
* (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
|
* (rand::random::<f64>() + 1.0);
|
||||||
ih.tx_count += 1;
|
ih.tx_count += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -2010,8 +2009,7 @@ impl CryptoServer {
|
|||||||
|
|
||||||
// Send ack – Implementing sending the empty acknowledgement here
|
// Send ack – Implementing sending the empty acknowledgement here
|
||||||
// instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data>
|
// instead of a generic PeerPtr::send(&Server, Option<&[u8]>) -> Either<EmptyData, Data>
|
||||||
// because data transmission is a stub currently. This software is supposed to be used
|
// because data transmission is a stub currently.
|
||||||
// as a key exchange service feeding a PSK into some classical (i.e. non post quantum)
|
|
||||||
let ses = peer
|
let ses = peer
|
||||||
.session()
|
.session()
|
||||||
.get_mut(self)
|
.get_mut(self)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use rosenpass_util::{
|
|||||||
mio::WriteWithFileDescriptors,
|
mio::WriteWithFileDescriptors,
|
||||||
zerocopy::ZerocopySliceExt,
|
zerocopy::ZerocopySliceExt,
|
||||||
};
|
};
|
||||||
use rustix::fd::{AsFd, AsRawFd};
|
use std::os::fd::{AsFd, AsRawFd};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use zerocopy::AsBytes;
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::fs::File;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
@@ -5,9 +6,10 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rosenpass::{app_server::AppServerTestBuilder, cli::CliArgs};
|
use rosenpass::{app_server::AppServerTestBuilder, cli::CliArgs, config::EXAMPLE_CONFIG};
|
||||||
use rosenpass_secret_memory::{Public, Secret};
|
use rosenpass_secret_memory::{Public, Secret};
|
||||||
use rosenpass_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
use rosenpass_wireguard_broker::{WireguardBrokerMio, WG_KEY_LEN, WG_PEER_LEN};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
@@ -134,6 +136,46 @@ fn run_server_client_exchange(
|
|||||||
client_terminate.send(()).unwrap();
|
client_terminate.send(()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify that EXAMPLE_CONFIG is correct
|
||||||
|
#[test]
|
||||||
|
fn check_example_config() {
|
||||||
|
setup_tests();
|
||||||
|
setup_logging();
|
||||||
|
|
||||||
|
let tmp_dir = tempdir().unwrap();
|
||||||
|
let config_path = tmp_dir.path().join("config.toml");
|
||||||
|
let mut config_file = File::create(config_path.to_owned()).unwrap();
|
||||||
|
|
||||||
|
config_file
|
||||||
|
.write_all(
|
||||||
|
EXAMPLE_CONFIG
|
||||||
|
.replace("/path/to", tmp_dir.path().to_str().unwrap())
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let output = test_bin::get_test_bin(BIN)
|
||||||
|
.args(["gen-keys"])
|
||||||
|
.arg(&config_path)
|
||||||
|
.output()
|
||||||
|
.expect("EXAMPLE_CONFIG not valid");
|
||||||
|
|
||||||
|
fs::copy(
|
||||||
|
tmp_dir.path().join("rp-public-key"),
|
||||||
|
tmp_dir.path().join("rp-peer-public-key"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let output = test_bin::get_test_bin(BIN)
|
||||||
|
.args(["validate"])
|
||||||
|
.arg(&config_path)
|
||||||
|
.output()
|
||||||
|
.expect("EXAMPLE_CONFIG not valid");
|
||||||
|
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert!(stderr.contains("has passed all logical checks"));
|
||||||
|
}
|
||||||
|
|
||||||
// check that we can exchange keys
|
// check that we can exchange keys
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64ct = { workspace = true }
|
base64ct = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
@@ -20,9 +22,9 @@ rosenpass-ciphers = { workspace = true }
|
|||||||
rosenpass-cipher-traits = { workspace = true }
|
rosenpass-cipher-traits = { workspace = true }
|
||||||
rosenpass-secret-memory = { workspace = true }
|
rosenpass-secret-memory = { workspace = true }
|
||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
rosenpass-wireguard-broker = {workspace = true}
|
rosenpass-wireguard-broker = { workspace = true }
|
||||||
|
|
||||||
tokio = {workspace = true}
|
tokio = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||||
ctrlc-async = "3.2"
|
ctrlc-async = "3.2"
|
||||||
@@ -35,8 +37,8 @@ netlink-packet-generic = "0.3"
|
|||||||
netlink-packet-wireguard = "0.2"
|
netlink-packet-wireguard = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = {workspace = true}
|
tempfile = { workspace = true }
|
||||||
stacker = {workspace = true}
|
stacker = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_memfd_secret = []
|
experiment_memfd_secret = []
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ pub enum Command {
|
|||||||
public_keys_dir: PathBuf,
|
public_keys_dir: PathBuf,
|
||||||
},
|
},
|
||||||
Exchange(ExchangeOptions),
|
Exchange(ExchangeOptions),
|
||||||
|
ExchangeConfig {
|
||||||
|
config_file: PathBuf,
|
||||||
|
},
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ enum CommandType {
|
|||||||
GenKey,
|
GenKey,
|
||||||
PubKey,
|
PubKey,
|
||||||
Exchange,
|
Exchange,
|
||||||
|
ExchangeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -32,9 +36,10 @@ fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
|||||||
Some(command) => match command {
|
Some(command) => match command {
|
||||||
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
||||||
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
||||||
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [ip <ip1>/<cidr1>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
||||||
|
CommandType::ExchangeConfig => Err(format!("{}\nUsage: rp exchange-config <CONFIG_FILE>", note)),
|
||||||
},
|
},
|
||||||
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange [ARGS]...", note)),
|
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange|exchange-config [ARGS]...", note)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +149,13 @@ impl ExchangeOptions {
|
|||||||
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"ip" => {
|
||||||
|
if let Some(ip) = args.next() {
|
||||||
|
options.ip = Some(ip);
|
||||||
|
} else {
|
||||||
|
return fatal("is option requires parameter", Some(CommandType::Exchange));
|
||||||
|
}
|
||||||
|
}
|
||||||
"listen" => {
|
"listen" => {
|
||||||
if let Some(addr) = args.next() {
|
if let Some(addr) = args.next() {
|
||||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||||
@@ -246,6 +258,21 @@ impl Cli {
|
|||||||
let options = ExchangeOptions::parse(&mut args)?;
|
let options = ExchangeOptions::parse(&mut args)?;
|
||||||
cli.command = Some(Command::Exchange(options));
|
cli.command = Some(Command::Exchange(options));
|
||||||
}
|
}
|
||||||
|
"exchange-config" => {
|
||||||
|
if cli.command.is_some() {
|
||||||
|
return fatal("Too many commands supplied", None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(config_file) = args.next() {
|
||||||
|
let config_file = PathBuf::from(config_file);
|
||||||
|
cli.command = Some(Command::ExchangeConfig { config_file });
|
||||||
|
} else {
|
||||||
|
return fatal(
|
||||||
|
"Required position argument: CONFIG_FILE",
|
||||||
|
Some(CommandType::ExchangeConfig),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
"help" => {
|
"help" => {
|
||||||
cli.command = Some(Command::Help);
|
cli.command = Some(Command::Help);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf};
|
use anyhow::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{net::SocketAddr, path::PathBuf, process::Command};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
use crate::key::WG_B64_LEN;
|
use crate::key::WG_B64_LEN;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct ExchangePeer {
|
pub struct ExchangePeer {
|
||||||
pub public_keys_dir: PathBuf,
|
pub public_keys_dir: PathBuf,
|
||||||
pub endpoint: Option<SocketAddr>,
|
pub endpoint: Option<SocketAddr>,
|
||||||
@@ -13,11 +19,12 @@ pub struct ExchangePeer {
|
|||||||
pub allowed_ips: Option<String>,
|
pub allowed_ips: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct ExchangeOptions {
|
pub struct ExchangeOptions {
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
pub private_keys_dir: PathBuf,
|
pub private_keys_dir: PathBuf,
|
||||||
pub dev: Option<String>,
|
pub dev: Option<String>,
|
||||||
|
pub ip: Option<String>,
|
||||||
pub listen: Option<SocketAddr>,
|
pub listen: Option<SocketAddr>,
|
||||||
pub peers: Vec<ExchangePeer>,
|
pub peers: Vec<ExchangePeer>,
|
||||||
}
|
}
|
||||||
@@ -131,6 +138,27 @@ mod netlink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
|
struct CleanupHandlers(
|
||||||
|
Arc<::futures::lock::Mutex<Vec<Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>>>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
|
impl CleanupHandlers {
|
||||||
|
fn new() -> Self {
|
||||||
|
CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![])))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn enqueue(&self, handler: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>) {
|
||||||
|
self.0.lock().await.push(Box::pin(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(self) -> Result<Vec<()>, Error> {
|
||||||
|
futures::future::try_join_all(self.0.lock().await.deref_mut()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -151,15 +179,50 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||||
tokio::spawn(connection);
|
tokio::spawn(connection);
|
||||||
|
|
||||||
let link_name = options.dev.unwrap_or("rosenpass0".to_string());
|
let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||||
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
||||||
|
|
||||||
|
let cleanup_handlers = CleanupHandlers::new();
|
||||||
|
let final_cleanup_handlers = (&cleanup_handlers).clone();
|
||||||
|
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
netlink::link_cleanup_standalone(link_index).await
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
ctrlc_async::set_async_handler(async move {
|
ctrlc_async::set_async_handler(async move {
|
||||||
netlink::link_cleanup_standalone(link_index)
|
final_cleanup_handlers
|
||||||
|
.run()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to clean up");
|
.expect("Failed to clean up");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(ip) = options.ip {
|
||||||
|
let dev = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("address")
|
||||||
|
.arg("add")
|
||||||
|
.arg(ip.clone())
|
||||||
|
.arg("dev")
|
||||||
|
.arg(dev.clone())
|
||||||
|
.status()
|
||||||
|
.expect("failed to configure ip");
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("address")
|
||||||
|
.arg("del")
|
||||||
|
.arg(ip)
|
||||||
|
.arg("dev")
|
||||||
|
.arg(dev)
|
||||||
|
.status()
|
||||||
|
.expect("failed to remove ip");
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// Deploy the classic wireguard private key
|
// Deploy the classic wireguard private key
|
||||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||||
tokio::spawn(connection);
|
tokio::spawn(connection);
|
||||||
@@ -254,6 +317,29 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
|||||||
broker_peer,
|
broker_peer,
|
||||||
peer.endpoint.map(|x| x.to_string()),
|
peer.endpoint.map(|x| x.to_string()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Configure routes
|
||||||
|
if let Some(allowed_ips) = peer.allowed_ips {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("replace")
|
||||||
|
.arg(allowed_ips.clone())
|
||||||
|
.arg("dev")
|
||||||
|
.arg(options.dev.clone().unwrap_or("rosenpass0".to_string()))
|
||||||
|
.status()
|
||||||
|
.expect("failed to configure route");
|
||||||
|
cleanup_handlers
|
||||||
|
.enqueue(Box::pin(async move {
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("del")
|
||||||
|
.arg(allowed_ips)
|
||||||
|
.status()
|
||||||
|
.expect("failed to remove ip");
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = srv.event_loop();
|
let out = srv.event_loop();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::process::exit;
|
use std::{fs, process::exit};
|
||||||
|
|
||||||
use cli::{Cli, Command};
|
use cli::{Cli, Command};
|
||||||
use exchange::exchange;
|
use exchange::exchange;
|
||||||
@@ -36,6 +36,13 @@ async fn main() {
|
|||||||
options.verbose = cli.verbose;
|
options.verbose = cli.verbose;
|
||||||
exchange(options).await
|
exchange(options).await
|
||||||
}
|
}
|
||||||
|
Command::ExchangeConfig { config_file } => {
|
||||||
|
let s: String = fs::read_to_string(config_file).expect("cannot read config");
|
||||||
|
let mut options: exchange::ExchangeOptions =
|
||||||
|
toml::from_str::<exchange::ExchangeOptions>(&s).expect("cannot parse config");
|
||||||
|
options.verbose = options.verbose || cli.verbose;
|
||||||
|
exchange(options).await
|
||||||
|
}
|
||||||
Command::Help => {
|
Command::Help => {
|
||||||
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ log = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
allocator-api2-tests = { workspace = true }
|
allocator-api2-tests = { workspace = true }
|
||||||
tempfile = {workspace = true}
|
tempfile = { workspace = true }
|
||||||
base64ct = {workspace = true}
|
base64ct = { workspace = true }
|
||||||
procspawn = {workspace = true}
|
procspawn = { workspace = true }
|
||||||
|
|||||||
2
systemd/rosenpass.target
Normal file
2
systemd/rosenpass.target
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass target
|
||||||
47
systemd/rosenpass@.service
Normal file
47
systemd/rosenpass@.service
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass key exchange for %I
|
||||||
|
Documentation=man:rosenpass(1)
|
||||||
|
Documentation=https://rosenpass.eu/docs
|
||||||
|
|
||||||
|
After=network-online.target nss-lookup.target sys-devices-virtual-net-%i.device
|
||||||
|
Wants=network-online.target nss-lookup.target
|
||||||
|
BindsTo=sys-devices-virtual-net-%i.device
|
||||||
|
PartOf=rosenpass.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=rosenpass exchange-config /etc/rosenpass/%i.toml
|
||||||
|
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN
|
||||||
|
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||||
|
DynamicUser=true
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
|
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
48
systemd/rp@.service
Normal file
48
systemd/rp@.service
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rosenpass key exchange for %I
|
||||||
|
Documentation=man:rosenpass(1)
|
||||||
|
Documentation=https://rosenpass.eu/docs
|
||||||
|
|
||||||
|
After=network-online.target nss-lookup.target
|
||||||
|
Wants=network-online.target nss-lookup.target
|
||||||
|
PartOf=rosenpass.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=rp exchange-config /etc/rosenpass/%i.toml
|
||||||
|
LoadCredential=pqpk:/etc/rosenpass/%i/pqpk
|
||||||
|
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||||
|
LoadCredential=wgsk:/etc/rosenpass/%i/wgsk
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN
|
||||||
|
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||||
|
DynamicUser=true
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
|
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
183
tests/systemd/rosenpass.nix
Normal file
183
tests/systemd/rosenpass.nix
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# This test is largely inspired from:
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/rosenpass.nix
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/wireguard/basic.nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
server = {
|
||||||
|
ip4 = "192.168.0.1";
|
||||||
|
ip6 = "fd00::1";
|
||||||
|
wg = {
|
||||||
|
ip4 = "10.23.42.1";
|
||||||
|
ip6 = "fc00::1";
|
||||||
|
public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
|
||||||
|
secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
|
||||||
|
listen = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client = {
|
||||||
|
ip4 = "192.168.0.2";
|
||||||
|
ip6 = "fd00::2";
|
||||||
|
wg = {
|
||||||
|
ip4 = "10.23.42.2";
|
||||||
|
ip6 = "fc00::2";
|
||||||
|
public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
|
||||||
|
secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
server_config = {
|
||||||
|
listen = [ "0.0.0.0:9999" ];
|
||||||
|
public_key = "/etc/rosenpass/rp0/pqpk";
|
||||||
|
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
|
||||||
|
verbosity = "Verbose";
|
||||||
|
peers = [{
|
||||||
|
device = "rp0";
|
||||||
|
peer = client.wg.public;
|
||||||
|
public_key = "/etc/rosenpass/rp0/peers/client/pqpk";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
client_config = {
|
||||||
|
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";
|
||||||
|
peers = [{
|
||||||
|
device = "rp0";
|
||||||
|
peer = server.wg.public;
|
||||||
|
public_key = "/etc/rosenpass/rp0/peers/server/pqpk";
|
||||||
|
endpoint = "${server.ip4}:9999";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = pkgs.runCommand "config" { } ''
|
||||||
|
mkdir -pv $out
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" server_config} $out/server
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" client_config} $out/client
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "rosenpass unit";
|
||||||
|
|
||||||
|
nodes =
|
||||||
|
let
|
||||||
|
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||||
|
# Need to work around a problem in recent systemd changes.
|
||||||
|
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||||
|
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||||
|
# This can potentially be removed in future nixpkgs updates
|
||||||
|
systemd.packages = [
|
||||||
|
(pkgs.runCommand "rosenpass" { } ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass@.service \
|
||||||
|
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.wireguard-tools}/bin@' |
|
||||||
|
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||||
|
sed 's@^ExecStart=rosenpass @ExecStart='"${pkgs.rosenpass}"'/bin/rosenpass @' > $out/lib/systemd/system/rosenpass@.service
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
networking.wireguard = {
|
||||||
|
enable = true;
|
||||||
|
interfaces.rp0 = {
|
||||||
|
ips = [ "${peer.wg.ip4}/32" "${peer.wg.ip6}/128" ];
|
||||||
|
privateKeyFile = "/etc/wireguard/wgsk";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
environment.etc."wireguard/wgsk".text = peer.wg.secret;
|
||||||
|
networking.interfaces.eth1 = {
|
||||||
|
ipv4.addresses = [{
|
||||||
|
address = peer.ip4;
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
ipv6.addresses = [{
|
||||||
|
address = peer.ip6;
|
||||||
|
prefixLength = 64;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
server = {
|
||||||
|
imports = [ (shared server) ];
|
||||||
|
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||||
|
networking.wireguard.interfaces.rp0 = {
|
||||||
|
listenPort = server.wg.listen;
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
allowedIPs = [ client.wg.ip4 client.wg.ip6 ];
|
||||||
|
publicKey = client.wg.public;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
client = {
|
||||||
|
imports = [ (shared client) ];
|
||||||
|
networking.wireguard.interfaces.rp0 = {
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
allowedIPs = [ "10.23.42.0/24" "fc00::/64" ];
|
||||||
|
publicKey = server.wg.public;
|
||||||
|
endpoint = "${server.ip4}:${toString server.wg.listen}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = { ... }: ''
|
||||||
|
from os import system
|
||||||
|
rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
with subtest("Key, Config, and Service Setup"):
|
||||||
|
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||||
|
# generate all the keys
|
||||||
|
system(f"{rosenpass} gen-keys --public-key {name}-pqpk --secret-key {name}-pqsk")
|
||||||
|
|
||||||
|
# copy private keys to our side
|
||||||
|
machine.copy_from_host(f"{name}-pqsk", "/etc/rosenpass/rp0/pqsk")
|
||||||
|
machine.copy_from_host(f"{name}-pqpk", "/etc/rosenpass/rp0/pqpk")
|
||||||
|
|
||||||
|
# copy public keys to other side
|
||||||
|
remote.copy_from_host(f"{name}-pqpk", f"/etc/rosenpass/rp0/peers/{name}/pqpk")
|
||||||
|
|
||||||
|
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/rp0.toml")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("wireguard-rp0.service")
|
||||||
|
|
||||||
|
with subtest("wg network test"):
|
||||||
|
client.succeed("wg show all preshared-keys | grep none", timeout=5);
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
|
||||||
|
with subtest("Set up rosenpass"):
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.succeed("systemctl start rosenpass@rp0.service")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("rosenpass@rp0.service")
|
||||||
|
|
||||||
|
|
||||||
|
with subtest("compare preshared keys"):
|
||||||
|
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
|
||||||
|
def get_psk(m):
|
||||||
|
psk = m.succeed("wg show rp0 preshared-keys | awk '{print $2}'")
|
||||||
|
psk = psk.strip()
|
||||||
|
assert len(psk.split()) == 1, "Only one PSK"
|
||||||
|
return psk
|
||||||
|
|
||||||
|
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||||
|
|
||||||
|
with subtest("rosenpass network test"):
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
'';
|
||||||
|
}
|
||||||
139
tests/systemd/rp.nix
Normal file
139
tests/systemd/rp.nix
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
server = {
|
||||||
|
ip4 = "192.168.0.1";
|
||||||
|
ip6 = "fd00::1";
|
||||||
|
wg = {
|
||||||
|
ip6 = "fc00::1";
|
||||||
|
listen = 10000;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client = {
|
||||||
|
ip4 = "192.168.0.2";
|
||||||
|
ip6 = "fd00::2";
|
||||||
|
wg = {
|
||||||
|
ip6 = "fc00::2";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
server_config = {
|
||||||
|
listen = "${server.ip4}:9999";
|
||||||
|
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||||
|
verbose = true;
|
||||||
|
dev = "test-rp-device0";
|
||||||
|
ip = "fc00::1/64";
|
||||||
|
peers = [{
|
||||||
|
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/client";
|
||||||
|
allowed_ips = "fc00::2";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
client_config = {
|
||||||
|
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||||
|
verbose = true;
|
||||||
|
dev = "test-rp-device0";
|
||||||
|
ip = "fc00::2/128";
|
||||||
|
peers = [{
|
||||||
|
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/server";
|
||||||
|
endpoint = "${server.ip4}:9999";
|
||||||
|
allowed_ips = "fc00::/64";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = pkgs.runCommand "config" { } ''
|
||||||
|
mkdir -pv $out
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" server_config} $out/server
|
||||||
|
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" client_config} $out/client
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "rp systemd unit";
|
||||||
|
|
||||||
|
nodes =
|
||||||
|
let
|
||||||
|
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||||
|
# Need to work around a problem in recent systemd changes.
|
||||||
|
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||||
|
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||||
|
# This can potentially be removed in future nixpkgs updates
|
||||||
|
systemd.packages = [
|
||||||
|
(pkgs.runCommand "rp@.service" { } ''
|
||||||
|
mkdir -p $out/lib/systemd/system
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||||
|
< ${pkgs.rosenpass}/lib/systemd/system/rp@.service \
|
||||||
|
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.iproute2}/bin:${pkgs.wireguard-tools}/bin@' |
|
||||||
|
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||||
|
sed 's@^ExecStart=rp @ExecStart='"${pkgs.rosenpass}"'/bin/rp @' > $out/lib/systemd/system/rp@.service
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
environment.systemPackages = [ pkgs.wireguard-tools ];
|
||||||
|
networking.interfaces.eth1 = {
|
||||||
|
ipv4.addresses = [{
|
||||||
|
address = peer.ip4;
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
ipv6.addresses = [{
|
||||||
|
address = peer.ip6;
|
||||||
|
prefixLength = 64;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
server = {
|
||||||
|
imports = [ (shared server) ];
|
||||||
|
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||||
|
};
|
||||||
|
client = {
|
||||||
|
imports = [ (shared client) ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = { ... }: ''
|
||||||
|
from os import system
|
||||||
|
rp = "${pkgs.rosenpass}/bin/rp"
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
with subtest("Key, Config, and Service Setup"):
|
||||||
|
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||||
|
# create all the keys
|
||||||
|
system(f"{rp} genkey {name}-sk")
|
||||||
|
system(f"{rp} pubkey {name}-sk {name}-pk")
|
||||||
|
|
||||||
|
# copy secret keys to our side
|
||||||
|
for file in ["pqpk", "pqsk", "wgsk"]:
|
||||||
|
machine.copy_from_host(f"{name}-sk/{file}", f"/etc/rosenpass/test-rp-device0/{file}")
|
||||||
|
# copy public keys to other side
|
||||||
|
for file in ["pqpk", "wgpk"]:
|
||||||
|
remote.copy_from_host(f"{name}-pk/{file}", f"/etc/rosenpass/test-rp-device0/peers/{name}/{file}")
|
||||||
|
|
||||||
|
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/test-rp-device0.toml")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.succeed("systemctl start rp@test-rp-device0.service")
|
||||||
|
|
||||||
|
for machine in [server, client]:
|
||||||
|
machine.wait_for_unit("rp@test-rp-device0.service")
|
||||||
|
|
||||||
|
with subtest("compare preshared keys"):
|
||||||
|
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||||
|
|
||||||
|
def get_psk(m):
|
||||||
|
psk = m.succeed("wg show test-rp-device0 preshared-keys | awk '{print $2}'")
|
||||||
|
psk = psk.strip()
|
||||||
|
assert len(psk.split()) == 1, "Only one PSK"
|
||||||
|
return psk
|
||||||
|
|
||||||
|
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||||
|
|
||||||
|
with subtest("network test"):
|
||||||
|
client.succeed("ping -c5 ${server.wg.ip6}")
|
||||||
|
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
#![recursion_limit = "256"]
|
||||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||||
|
|
||||||
#[cfg(doctest)]
|
#[cfg(doctest)]
|
||||||
|
|||||||
@@ -5,23 +5,70 @@ use crate::CondenseBeside;
|
|||||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||||
|
|
||||||
impl<Val, Ret> Beside<Val, Ret> {
|
impl<Val, Ret> Beside<Val, Ret> {
|
||||||
|
/// Get an immutable reference to the destination value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, 2);
|
||||||
|
/// assert_eq!(beside.dest(), &1);
|
||||||
|
/// ```
|
||||||
pub fn dest(&self) -> &Val {
|
pub fn dest(&self) -> &Val {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an immutable reference to the return value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, 2);
|
||||||
|
/// assert_eq!(beside.ret(), &2);
|
||||||
|
/// ```
|
||||||
pub fn ret(&self) -> &Ret {
|
pub fn ret(&self) -> &Ret {
|
||||||
&self.1
|
&self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the destination value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let mut beside = Beside(1, 2);
|
||||||
|
/// *beside.dest_mut() = 3;
|
||||||
|
/// assert_eq!(beside.dest(), &3);
|
||||||
|
/// ```
|
||||||
pub fn dest_mut(&mut self) -> &mut Val {
|
pub fn dest_mut(&mut self) -> &mut Val {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the return value
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
///
|
||||||
|
/// let mut beside = Beside(1, 2);
|
||||||
|
/// *beside.ret_mut() = 3;
|
||||||
|
/// assert_eq!(beside.ret(), &3);
|
||||||
|
/// ```
|
||||||
pub fn ret_mut(&mut self) -> &mut Ret {
|
pub fn ret_mut(&mut self) -> &mut Ret {
|
||||||
&mut self.1
|
&mut self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform beside condensation. See [CondenseBeside]
|
/// Perform beside condensation. See [CondenseBeside]
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_to::Beside;
|
||||||
|
/// use rosenpass_to::CondenseBeside;
|
||||||
|
///
|
||||||
|
/// let beside = Beside(1, ());
|
||||||
|
/// assert_eq!(beside.condense(), 1);
|
||||||
|
/// ```
|
||||||
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||||
where
|
where
|
||||||
Ret: CondenseBeside<Val>,
|
Ret: CondenseBeside<Val>,
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||||
/// condense trait.
|
/// condense trait.
|
||||||
pub trait CondenseBeside<Val> {
|
pub trait CondenseBeside<Val> {
|
||||||
|
/// The type that results from condensation.
|
||||||
type Condensed;
|
type Condensed;
|
||||||
|
|
||||||
|
/// Takes ownership of `self` and condenses it with the given value.
|
||||||
fn condense(self, ret: Val) -> Self::Condensed;
|
fn condense(self, ret: Val) -> Self::Condensed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/// Helper performing explicit unsized coercion.
|
/// Helper performing explicit unsized coercion.
|
||||||
/// Used by the [to](crate::to()) function.
|
/// Used by the [to](crate::to()) function.
|
||||||
pub trait DstCoercion<Dst: ?Sized> {
|
pub trait DstCoercion<Dst: ?Sized> {
|
||||||
|
/// Performs an explicit coercion to the destination type.
|
||||||
fn coerce_dest(&mut self) -> &mut Dst;
|
fn coerce_dest(&mut self) -> &mut Dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
use crate::{Beside, CondenseBeside};
|
use crate::{Beside, CondenseBeside};
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
// The To trait is the core of the to crate; most functions with destinations will either return
|
/// 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,
|
/// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
||||||
// Return_value`.
|
/// Return_value`.
|
||||||
//
|
///
|
||||||
// A quick way to implement a function with destination is to use the
|
/// A quick way to implement a function with destination is to use the
|
||||||
// [with_destination(|param: &mut Type| ...)] higher order function.
|
/// [with_destination(|param: &mut Type| ...)] higher order function.
|
||||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||||
|
/// Writes self to the destination `out` and returns a value of type `Ret`.
|
||||||
|
///
|
||||||
|
/// This is the core method that must be implemented by all types implementing `To`.
|
||||||
fn to(self, out: &mut Dst) -> Ret;
|
fn to(self, out: &mut Dst) -> Ret;
|
||||||
|
|
||||||
/// Generate a destination on the fly with a lambda.
|
/// Generate a destination on the fly with a lambda.
|
||||||
|
|||||||
@@ -1,20 +1,38 @@
|
|||||||
use crate::To;
|
use crate::To;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// 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`.
|
||||||
|
///
|
||||||
|
/// # 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`
|
||||||
struct ToClosure<Dst, Ret, Fun>
|
struct ToClosure<Dst, Ret, Fun>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
Fun: FnOnce(&mut Dst) -> Ret,
|
Fun: FnOnce(&mut Dst) -> Ret,
|
||||||
{
|
{
|
||||||
|
/// The function to call.
|
||||||
fun: Fun,
|
fun: Fun,
|
||||||
|
/// Phantom data to hold the destination type
|
||||||
_val: PhantomData<Box<Dst>>,
|
_val: PhantomData<Box<Dst>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>
|
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||||
where
|
where
|
||||||
Dst: ?Sized,
|
Dst: ?Sized,
|
||||||
Fun: FnOnce(&mut Dst) -> Ret,
|
Fun: FnOnce(&mut Dst) -> Ret,
|
||||||
{
|
{
|
||||||
|
/// Execute the wrapped closure with the given destination
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `out` - Mutable reference to the destination
|
||||||
fn to(self, out: &mut Dst) -> Ret {
|
fn to(self, out: &mut Dst) -> Ret {
|
||||||
(self.fun)(out)
|
(self.fun)(out)
|
||||||
}
|
}
|
||||||
@@ -22,6 +40,14 @@ where
|
|||||||
|
|
||||||
/// Used to create a function with destination.
|
/// Used to create a function with destination.
|
||||||
///
|
///
|
||||||
|
/// Creates a wrapper that implements the `To` trait for a closure that
|
||||||
|
/// operates on a destination type.
|
||||||
|
///
|
||||||
|
/// # 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`
|
||||||
|
///
|
||||||
/// See the tutorial in [readme.me]..
|
/// See the tutorial in [readme.me]..
|
||||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
//! Utilities for working with Base64
|
||||||
|
|
||||||
use base64ct::{Base64, Decoder as B64Reader, Encoder as B64Writer};
|
use base64ct::{Base64, Decoder as B64Reader, Encoder as B64Writer};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
/// Formatter that displays its input as base64.
|
||||||
|
///
|
||||||
|
/// Use through [B64Display].
|
||||||
pub struct B64DisplayHelper<'a, const F: usize>(&'a [u8]);
|
pub struct B64DisplayHelper<'a, const F: usize>(&'a [u8]);
|
||||||
|
|
||||||
impl<const F: usize> Display for B64DisplayHelper<'_, F> {
|
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 {
|
pub trait B64Display {
|
||||||
|
/// Display this value as base64
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [B64Display].
|
||||||
fn fmt_b64<const F: usize>(&self) -> B64DisplayHelper<F>;
|
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<()> {
|
pub fn b64_decode(input: &[u8], output: &mut [u8]) -> anyhow::Result<()> {
|
||||||
let mut reader = B64Reader::<Base64>::new(input).map_err(|e| anyhow::anyhow!(e))?;
|
let mut reader = B64Reader::<Base64>::new(input).map_err(|e| anyhow::anyhow!(e))?;
|
||||||
match reader.decode(output) {
|
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> {
|
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))?;
|
let mut writer = B64Writer::<Base64>::new(output).map_err(|e| anyhow::anyhow!(e))?;
|
||||||
writer.encode(input).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::{
|
use crate::{
|
||||||
functional::ApplyExt,
|
functional::ApplyExt,
|
||||||
mem::{SwapWithDefaultExt, SwapWithExt},
|
mem::{SwapWithDefaultExt, SwapWithExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
/// Errors returned by [ConstructionSite::erect]
|
||||||
|
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
|
||||||
pub enum ConstructionSiteErectError<E> {
|
pub enum ConstructionSiteErectError<E> {
|
||||||
|
/// Attempted to erect an empty construction site
|
||||||
#[error("Construction site is void")]
|
#[error("Construction site is void")]
|
||||||
IsVoid,
|
IsVoid,
|
||||||
|
/// Attempted to erect a construction that is already standing
|
||||||
#[error("Construction is already built")]
|
#[error("Construction is already built")]
|
||||||
AlreadyBuilt,
|
AlreadyBuilt,
|
||||||
|
/// Other error
|
||||||
#[error("Other construction site error {0:?}")]
|
#[error("Other construction site error {0:?}")]
|
||||||
Other(#[from] E),
|
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 {
|
pub trait Build<T>: Sized {
|
||||||
|
/// Error returned by the builder
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Build the type
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [Self].
|
||||||
fn build(self) -> Result<T, Self::Error>;
|
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>
|
pub enum ConstructionSite<Builder, T>
|
||||||
where
|
where
|
||||||
Builder: Build<T>,
|
Builder: Build<T>,
|
||||||
{
|
{
|
||||||
|
/// The site is empty
|
||||||
Void,
|
Void,
|
||||||
|
/// The site is being built
|
||||||
Builder(Builder),
|
Builder(Builder),
|
||||||
|
/// The site has been built and is now finished
|
||||||
Product(T),
|
Product(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes the construction site as [ConstructionSite::Void]
|
||||||
impl<Builder, T> Default for ConstructionSite<Builder, T>
|
impl<Builder, T> Default for ConstructionSite<Builder, T>
|
||||||
where
|
where
|
||||||
Builder: Build<T>,
|
Builder: Build<T>,
|
||||||
@@ -41,22 +171,189 @@ impl<Builder, T> ConstructionSite<Builder, T>
|
|||||||
where
|
where
|
||||||
Builder: Build<T>,
|
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 {
|
pub fn void() -> Self {
|
||||||
Self::Void
|
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 {
|
pub fn new(builder: Builder) -> Self {
|
||||||
Self::Builder(builder)
|
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 {
|
pub fn from_product(value: T) -> Self {
|
||||||
Self::Product(value)
|
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 {
|
pub fn take(&mut self) -> Self {
|
||||||
self.swap_with_default()
|
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
|
pub fn modify_taken_with_return<R, F>(&mut self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(Self) -> (Self, R),
|
F: FnOnce(Self) -> (Self, R),
|
||||||
@@ -66,6 +363,53 @@ where
|
|||||||
res
|
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)
|
pub fn modify_taken<F>(&mut self, f: F)
|
||||||
where
|
where
|
||||||
F: FnOnce(Self) -> Self,
|
F: FnOnce(Self) -> Self,
|
||||||
@@ -73,6 +417,42 @@ where
|
|||||||
self.take().apply(f).swap_with_mut(self)
|
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)]
|
#[allow(clippy::result_unit_err)]
|
||||||
pub fn erect(&mut self) -> Result<(), ConstructionSiteErectError<Builder::Error>> {
|
pub fn erect(&mut self) -> Result<(), ConstructionSiteErectError<Builder::Error>> {
|
||||||
self.modify_taken_with_return(|site| {
|
self.modify_taken_with_return(|site| {
|
||||||
@@ -98,6 +478,31 @@ where
|
|||||||
/// Returns `true` if the construction site is [`Void`].
|
/// Returns `true` if the construction site is [`Void`].
|
||||||
///
|
///
|
||||||
/// [`Void`]: ConstructionSite::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]
|
#[must_use]
|
||||||
pub fn is_void(&self) -> bool {
|
pub fn is_void(&self) -> bool {
|
||||||
matches!(self, Self::Void)
|
matches!(self, Self::Void)
|
||||||
@@ -106,19 +511,95 @@ where
|
|||||||
/// Returns `true` if the construction site is [`InProgress`].
|
/// Returns `true` if the construction site is [`InProgress`].
|
||||||
///
|
///
|
||||||
/// [`InProgress`]: ConstructionSite::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]
|
#[must_use]
|
||||||
pub fn in_progess(&self) -> bool {
|
pub fn in_progress(&self) -> bool {
|
||||||
matches!(self, Self::Builder(..))
|
matches!(self, Self::Builder(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the construction site is [`Done`].
|
/// Returns `true` if the construction site is [`Done`].
|
||||||
///
|
///
|
||||||
/// [`Done`]: ConstructionSite::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]
|
#[must_use]
|
||||||
pub fn is_available(&self) -> bool {
|
pub fn is_available(&self) -> bool {
|
||||||
matches!(self, Self::Product(..))
|
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> {
|
pub fn into_builder(self) -> Option<Builder> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
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> {
|
pub fn builder_ref(&self) -> Option<&Builder> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
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> {
|
pub fn builder_mut(&mut self) -> Option<&mut Builder> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
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> {
|
pub fn into_product(self) -> Option<T> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
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> {
|
pub fn product_ref(&self) -> Option<&T> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
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> {
|
pub fn product_mut(&mut self) -> Option<&mut T> {
|
||||||
use ConstructionSite as S;
|
use ConstructionSite as S;
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// A simple for loop to repeat a $body a number of times
|
/// A simple for loop to repeat a $body a number of times
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::repeat;
|
||||||
|
/// let mut sum = 0;
|
||||||
|
/// repeat!(10, {
|
||||||
|
/// sum += 1;
|
||||||
|
/// });
|
||||||
|
/// assert_eq!(sum, 10);
|
||||||
|
/// ```
|
||||||
macro_rules! repeat {
|
macro_rules! repeat {
|
||||||
($times:expr, $body:expr) => {
|
($times:expr, $body:expr) => {
|
||||||
for _ in 0..($times) {
|
for _ in 0..($times) {
|
||||||
@@ -12,6 +23,23 @@ macro_rules! repeat {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// Return unless the condition $cond is true, with return value $val, if given.
|
/// Return unless the condition $cond is true, with return value $val, if given.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::return_unless;
|
||||||
|
/// fn test_fn() -> i32 {
|
||||||
|
/// return_unless!(true, 1);
|
||||||
|
/// 0
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(test_fn(), 0);
|
||||||
|
|
||||||
|
/// fn test_fn2() -> i32 {
|
||||||
|
/// return_unless!(false, 1);
|
||||||
|
/// 0
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(test_fn2(), 1);
|
||||||
|
/// ```
|
||||||
macro_rules! return_unless {
|
macro_rules! return_unless {
|
||||||
($cond:expr) => {
|
($cond:expr) => {
|
||||||
if !($cond) {
|
if !($cond) {
|
||||||
@@ -27,6 +55,23 @@ macro_rules! return_unless {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// Return if the condition $cond is true, with return value $val, if given.
|
/// Return if the condition $cond is true, with return value $val, if given.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::return_if;
|
||||||
|
/// fn test_fn() -> i32 {
|
||||||
|
/// return_if!(true, 1);
|
||||||
|
/// 0
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(test_fn(), 1);
|
||||||
|
|
||||||
|
/// fn test_fn2() -> i32 {
|
||||||
|
/// return_if!(false, 1);
|
||||||
|
/// 0
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(test_fn2(), 0);
|
||||||
|
/// ```
|
||||||
macro_rules! return_if {
|
macro_rules! return_if {
|
||||||
($cond:expr) => {
|
($cond:expr) => {
|
||||||
if $cond {
|
if $cond {
|
||||||
@@ -42,6 +87,27 @@ macro_rules! return_if {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// Break unless the condition is true, from the loop with label $val, if given.
|
/// Break unless the condition is true, from the loop with label $val, if given.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::break_if;
|
||||||
|
/// let mut sum = 0;
|
||||||
|
/// for i in 0..10 {
|
||||||
|
/// break_if!(i == 5);
|
||||||
|
/// sum += 1;
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(sum, 5);
|
||||||
|
|
||||||
|
/// let mut sum = 0;
|
||||||
|
/// 'one: for _ in 0..10 {
|
||||||
|
/// for j in 0..20 {
|
||||||
|
/// break_if!(j == 5, 'one);
|
||||||
|
/// sum += 1;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(sum, 5);
|
||||||
|
/// ```
|
||||||
macro_rules! break_if {
|
macro_rules! break_if {
|
||||||
($cond:expr) => {
|
($cond:expr) => {
|
||||||
if $cond {
|
if $cond {
|
||||||
@@ -57,6 +123,25 @@ macro_rules! break_if {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
/// Continue if the condition is true, in the loop with label $val, if given.
|
/// Continue if the condition is true, in the loop with label $val, if given.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::continue_if;
|
||||||
|
/// let mut sum = 0;
|
||||||
|
/// for i in 0..10 {
|
||||||
|
/// continue_if!(i == 5);
|
||||||
|
/// sum += 1;
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(sum, 9);
|
||||||
|
|
||||||
|
/// let mut sum = 0;
|
||||||
|
/// 'one: for i in 0..10 {
|
||||||
|
/// continue_if!(i == 5, 'one);
|
||||||
|
/// sum += 1;
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(sum, 9);
|
||||||
|
/// ```
|
||||||
macro_rules! continue_if {
|
macro_rules! continue_if {
|
||||||
($cond:expr) => {
|
($cond:expr) => {
|
||||||
if $cond {
|
if $cond {
|
||||||
@@ -69,81 +154,3 @@ macro_rules! continue_if {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn test_repeat() {
|
|
||||||
let mut sum = 0;
|
|
||||||
repeat!(10, {
|
|
||||||
sum += 1;
|
|
||||||
});
|
|
||||||
assert_eq!(sum, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_return_unless() {
|
|
||||||
fn test_fn() -> i32 {
|
|
||||||
return_unless!(true, 1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
assert_eq!(test_fn(), 0);
|
|
||||||
|
|
||||||
fn test_fn2() -> i32 {
|
|
||||||
return_unless!(false, 1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
assert_eq!(test_fn2(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_return_if() {
|
|
||||||
fn test_fn() -> i32 {
|
|
||||||
return_if!(true, 1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
assert_eq!(test_fn(), 1);
|
|
||||||
|
|
||||||
fn test_fn2() -> i32 {
|
|
||||||
return_if!(false, 1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
assert_eq!(test_fn2(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_break_if() {
|
|
||||||
let mut sum = 0;
|
|
||||||
for i in 0..10 {
|
|
||||||
break_if!(i == 5);
|
|
||||||
sum += 1;
|
|
||||||
}
|
|
||||||
assert_eq!(sum, 5);
|
|
||||||
|
|
||||||
let mut sum = 0;
|
|
||||||
'one: for _ in 0..10 {
|
|
||||||
for j in 0..20 {
|
|
||||||
break_if!(j == 5, 'one);
|
|
||||||
sum += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(sum, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_continue_if() {
|
|
||||||
let mut sum = 0;
|
|
||||||
for i in 0..10 {
|
|
||||||
continue_if!(i == 5);
|
|
||||||
sum += 1;
|
|
||||||
}
|
|
||||||
assert_eq!(sum, 9);
|
|
||||||
|
|
||||||
let mut sum = 0;
|
|
||||||
'one: for i in 0..10 {
|
|
||||||
continue_if!(i == 5, 'one);
|
|
||||||
sum += 1;
|
|
||||||
}
|
|
||||||
assert_eq!(sum, 9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
232
util/src/fd.rs
232
util/src/fd.rs
@@ -1,17 +1,44 @@
|
|||||||
|
//! Utilities for working with file descriptors
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use rustix::{
|
use rustix::io::fcntl_dupfd_cloexec;
|
||||||
fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
|
use std::os::fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||||
io::fcntl_dupfd_cloexec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{mem::Forgetting, result::OkExt};
|
use crate::{mem::Forgetting, result::OkExt};
|
||||||
|
|
||||||
/// Prepare a file descriptor for use in Rust code.
|
/// Prepare a file descriptor for use in Rust code.
|
||||||
///
|
///
|
||||||
|
|
||||||
/// Checks if the file descriptor is valid and duplicates it to a new file descriptor.
|
/// Checks if the file descriptor is valid and duplicates it to a new file descriptor.
|
||||||
/// The old file descriptor is masked to avoid potential use after free (on file descriptor)
|
/// The old file descriptor is masked to avoid potential use after free (on file descriptor)
|
||||||
/// in case the given file descriptor is still used somewhere
|
/// in case the given file descriptor is still used somewhere
|
||||||
|
///
|
||||||
|
/// # Panic and safety
|
||||||
|
///
|
||||||
|
/// Will panic if the given file descriptor is negative of or larger than
|
||||||
|
/// the file descriptor numbers permitted by the operating system.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io::Write;
|
||||||
|
/// use std::os::fd::{IntoRawFd, AsRawFd};
|
||||||
|
/// use tempfile::tempdir;
|
||||||
|
/// use rosenpass_util::fd::{claim_fd, FdIo};
|
||||||
|
///
|
||||||
|
/// // Open a file and turn it into a raw file descriptor
|
||||||
|
/// let orig = tempfile::tempfile()?.into_raw_fd();
|
||||||
|
///
|
||||||
|
/// // Reclaim that file and ready it for reading
|
||||||
|
/// let mut claimed = FdIo(claim_fd(orig)?);
|
||||||
|
///
|
||||||
|
/// // A different file descriptor is used
|
||||||
|
/// assert!(orig.as_raw_fd() != claimed.0.as_raw_fd());
|
||||||
|
///
|
||||||
|
/// // Write some data
|
||||||
|
/// claimed.write_all(b"Hello, World!")?;
|
||||||
|
///
|
||||||
|
/// Ok::<(), std::io::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn claim_fd(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
pub fn claim_fd(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
||||||
let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?;
|
let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?;
|
||||||
mask_fd(fd)?;
|
mask_fd(fd)?;
|
||||||
@@ -22,7 +49,32 @@ pub fn claim_fd(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
|||||||
///
|
///
|
||||||
/// Checks if the file descriptor is valid.
|
/// Checks if the file descriptor is valid.
|
||||||
///
|
///
|
||||||
/// Unlike [claim_fd], this will reuse the same file descriptor identifier instead of masking it.
|
/// Unlike [claim_fd], this will try to reuse the same file descriptor identifier instead of masking it.
|
||||||
|
///
|
||||||
|
/// # Panic and safety
|
||||||
|
///
|
||||||
|
/// Will panic if the given file descriptor is negative of or larger than
|
||||||
|
/// the file descriptor numbers permitted by the operating system.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io::Write;
|
||||||
|
/// use std::os::fd::IntoRawFd;
|
||||||
|
/// use tempfile::tempdir;
|
||||||
|
/// use rosenpass_util::fd::{claim_fd_inplace, FdIo};
|
||||||
|
///
|
||||||
|
/// // Open a file and turn it into a raw file descriptor
|
||||||
|
/// let fd = tempfile::tempfile()?.into_raw_fd();
|
||||||
|
///
|
||||||
|
/// // Reclaim that file and ready it for reading
|
||||||
|
/// let mut fd = FdIo(claim_fd_inplace(fd)?);
|
||||||
|
///
|
||||||
|
/// // Write some data
|
||||||
|
/// fd.write_all(b"Hello, World!")?;
|
||||||
|
///
|
||||||
|
/// Ok::<(), std::io::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
||||||
let mut new = unsafe { OwnedFd::from_raw_fd(fd) };
|
let mut new = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||||
let tmp = clone_fd_cloexec(&new)?;
|
let tmp = clone_fd_cloexec(&new)?;
|
||||||
@@ -30,6 +82,13 @@ pub fn claim_fd_inplace(fd: RawFd) -> rustix::io::Result<OwnedFd> {
|
|||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Will close the given file descriptor and overwrite
|
||||||
|
/// it with a masking file descriptor (see [open_nullfd]) to prevent accidental reuse.
|
||||||
|
///
|
||||||
|
/// # Panic and safety
|
||||||
|
///
|
||||||
|
/// Will panic if the given file descriptor is negative of or larger than
|
||||||
|
/// the file descriptor numbers permitted by the operating system.
|
||||||
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
||||||
// Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting,
|
// 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
|
// it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd
|
||||||
@@ -37,11 +96,17 @@ pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> {
|
|||||||
clone_fd_to_cloexec(open_nullfd()?, &mut owned)
|
clone_fd_to_cloexec(open_nullfd()?, &mut owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Duplicate a file descriptor, setting the close on exec flag
|
||||||
pub fn clone_fd_cloexec<Fd: AsFd>(fd: Fd) -> rustix::io::Result<OwnedFd> {
|
pub fn clone_fd_cloexec<Fd: AsFd>(fd: Fd) -> rustix::io::Result<OwnedFd> {
|
||||||
const MINFD: RawFd = 3; // Avoid stdin, stdout, and stderr
|
/// Avoid stdin, stdout, and stderr
|
||||||
|
const MINFD: RawFd = 3;
|
||||||
fcntl_dupfd_cloexec(fd, MINFD)
|
fcntl_dupfd_cloexec(fd, MINFD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Duplicate a file descriptor, setting the close on exec flag.
|
||||||
|
///
|
||||||
|
/// This is slightly different from [clone_fd_cloexec], as this function supports specifying an
|
||||||
|
/// explicit destination file descriptor.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
|
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
|
||||||
use rustix::io::{dup3, DupFlags};
|
use rustix::io::{dup3, DupFlags};
|
||||||
@@ -49,6 +114,10 @@ pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::R
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
/// Duplicate a file descriptor, setting the close on exec flag.
|
||||||
|
///
|
||||||
|
/// This is slightly different from [clone_fd_cloexec], as this function supports specifying an
|
||||||
|
/// explicit destination file descriptor.
|
||||||
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
|
pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> {
|
||||||
use rustix::io::{dup2, fcntl_setfd, FdFlags};
|
use rustix::io::{dup2, fcntl_setfd, FdFlags};
|
||||||
dup2(&fd, new)?;
|
dup2(&fd, new)?;
|
||||||
@@ -56,7 +125,21 @@ pub fn clone_fd_to_cloexec<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> rustix::io::R
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor
|
/// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor
|
||||||
/// writing
|
/// writing.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The behavior of the file descriptor when being written to or from is undefined.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::{fs::File, io::Write, os::fd::IntoRawFd};
|
||||||
|
/// use rustix::fd::FromRawFd;
|
||||||
|
/// use rosenpass_util::fd::open_nullfd;
|
||||||
|
///
|
||||||
|
/// let nullfd = open_nullfd().unwrap();
|
||||||
|
/// ```
|
||||||
pub fn open_nullfd() -> rustix::io::Result<OwnedFd> {
|
pub fn open_nullfd() -> rustix::io::Result<OwnedFd> {
|
||||||
use rustix::fs::{open, Mode, OFlags};
|
use rustix::fs::{open, Mode, OFlags};
|
||||||
// TODO: Add tests showing that this will throw errors on use
|
// TODO: Add tests showing that this will throw errors on use
|
||||||
@@ -64,8 +147,24 @@ pub fn open_nullfd() -> rustix::io::Result<OwnedFd> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert low level errors into std::io::Error
|
/// Convert low level errors into std::io::Error
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io::ErrorKind as EK;
|
||||||
|
/// use rustix::io::Errno;
|
||||||
|
/// use rosenpass_util::fd::IntoStdioErr;
|
||||||
|
///
|
||||||
|
/// let e = Errno::INTR.into_stdio_err();
|
||||||
|
/// assert!(matches!(e.kind(), EK::Interrupted));
|
||||||
|
///
|
||||||
|
/// let r : rustix::io::Result<()> = Err(Errno::INTR);
|
||||||
|
/// assert!(matches!(r, Err(e) if e.kind() == EK::Interrupted));
|
||||||
|
/// ```
|
||||||
pub trait IntoStdioErr {
|
pub trait IntoStdioErr {
|
||||||
|
/// Target type produced (e.g. std::io:Error or std::io::Result depending on context
|
||||||
type Target;
|
type Target;
|
||||||
|
/// Convert low level errors to
|
||||||
fn into_stdio_err(self) -> Self::Target;
|
fn into_stdio_err(self) -> Self::Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +185,10 @@ impl<T> IntoStdioErr for rustix::io::Result<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read and write directly from a file descriptor
|
/// Read and write directly from a file descriptor
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [claim_fd].
|
||||||
pub struct FdIo<Fd: AsFd>(pub Fd);
|
pub struct FdIo<Fd: AsFd>(pub Fd);
|
||||||
|
|
||||||
impl<Fd: AsFd> std::io::Read for FdIo<Fd> {
|
impl<Fd: AsFd> std::io::Read for FdIo<Fd> {
|
||||||
@@ -104,7 +207,17 @@ impl<Fd: AsFd> std::io::Write for FdIo<Fd> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helpers for accessing stat(2) information
|
||||||
pub trait StatExt {
|
pub trait StatExt {
|
||||||
|
/// Check if the file is a socket
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::fd::StatExt;
|
||||||
|
/// assert!(rustix::fs::stat("/")?.is_socket() == false);
|
||||||
|
/// Ok::<(), rustix::io::Errno>(())
|
||||||
|
/// ````
|
||||||
fn is_socket(&self) -> bool;
|
fn is_socket(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +229,21 @@ impl StatExt for rustix::fs::Stat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helpers for accessing stat(2) information on an open file descriptor
|
||||||
pub trait TryStatExt {
|
pub trait TryStatExt {
|
||||||
|
/// Error type returned by operations
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Check if the file is a socket
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::fd::TryStatExt;
|
||||||
|
/// let fd = rustix::fs::open("/", rustix::fs::OFlags::empty(), rustix::fs::Mode::empty())?;
|
||||||
|
/// assert!(matches!(fd.is_socket(), Ok(false)));
|
||||||
|
/// Ok::<(), rustix::io::Errno>(())
|
||||||
|
/// ````
|
||||||
fn is_socket(&self) -> Result<bool, Self::Error>;
|
fn is_socket(&self) -> Result<bool, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +258,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine the type of socket a file descriptor represents
|
||||||
pub trait GetSocketType {
|
pub trait GetSocketType {
|
||||||
|
/// Error type returned by operations in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Look up the socket; see [rustix::net::sockopt::get_socket_type]
|
||||||
fn socket_type(&self) -> Result<rustix::net::SocketType, Self::Error>;
|
fn socket_type(&self) -> Result<rustix::net::SocketType, Self::Error>;
|
||||||
|
/// Checks if the socket is a datagram socket
|
||||||
fn is_datagram_socket(&self) -> Result<bool, Self::Error> {
|
fn is_datagram_socket(&self) -> Result<bool, Self::Error> {
|
||||||
use rustix::net::SocketType;
|
use rustix::net::SocketType;
|
||||||
matches!(self.socket_type()?, SocketType::DGRAM).ok()
|
matches!(self.socket_type()?, SocketType::DGRAM).ok()
|
||||||
}
|
}
|
||||||
|
/// Checks if the socket is a stream socket
|
||||||
fn is_stream_socket(&self) -> Result<bool, Self::Error> {
|
fn is_stream_socket(&self) -> Result<bool, Self::Error> {
|
||||||
Ok(self.socket_type()? == rustix::net::SocketType::STREAM)
|
Ok(self.socket_type()? == rustix::net::SocketType::STREAM)
|
||||||
}
|
}
|
||||||
@@ -155,13 +286,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Distinguish different socket address familys; e.g. IP and unix sockets
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub trait GetSocketDomain {
|
pub trait GetSocketDomain {
|
||||||
|
/// Error type returned by operations in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Retrieve the socket domain (address family)
|
||||||
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
|
fn socket_domain(&self) -> Result<rustix::net::AddressFamily, Self::Error>;
|
||||||
|
/// Alias for [socket_domain]
|
||||||
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
|
fn socket_address_family(&self) -> Result<rustix::net::AddressFamily, Self::Error> {
|
||||||
self.socket_domain()
|
self.socket_domain()
|
||||||
}
|
}
|
||||||
|
/// Check if the underlying socket is a unix domain socket
|
||||||
fn is_unix_socket(&self) -> Result<bool, Self::Error> {
|
fn is_unix_socket(&self) -> Result<bool, Self::Error> {
|
||||||
Ok(self.socket_domain()? == rustix::net::AddressFamily::UNIX)
|
Ok(self.socket_domain()? == rustix::net::AddressFamily::UNIX)
|
||||||
}
|
}
|
||||||
@@ -179,10 +315,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Distinguish different types of unix sockets
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub trait GetUnixSocketType {
|
pub trait GetUnixSocketType {
|
||||||
|
/// Error type returned by operations in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Check if the socket is a unix stream socket
|
||||||
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
|
fn is_unix_stream_socket(&self) -> Result<bool, Self::Error>;
|
||||||
|
/// Returns Ok(()) only if the underlying socket is a unix stream socket
|
||||||
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
|
fn demand_unix_stream_socket(&self) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,14 +350,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
/// Distinguish between different network socket protocols (e.g. tcp, udp)
|
||||||
pub trait GetSocketProtocol {
|
pub trait GetSocketProtocol {
|
||||||
|
/// Retrieve the socket protocol
|
||||||
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
|
fn socket_protocol(&self) -> Result<Option<rustix::net::Protocol>, rustix::io::Errno>;
|
||||||
|
/// Check if the socket is a udp socket
|
||||||
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
|
fn is_udp_socket(&self) -> Result<bool, rustix::io::Errno> {
|
||||||
self.socket_protocol()?
|
self.socket_protocol()?
|
||||||
.map(|p| p == rustix::net::ipproto::UDP)
|
.map(|p| p == rustix::net::ipproto::UDP)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
/// Return Ok(()) only if the socket is a udp socket
|
||||||
fn demand_udp_socket(&self) -> anyhow::Result<()> {
|
fn demand_udp_socket(&self) -> anyhow::Result<()> {
|
||||||
match self.socket_protocol() {
|
match self.socket_protocol() {
|
||||||
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),
|
Ok(Some(rustix::net::ipproto::UDP)) => Ok(()),
|
||||||
@@ -243,58 +387,58 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs::{read_to_string, File};
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::fd::IntoRawFd;
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_claim_fd() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let path = tmp_dir.path().join("test");
|
|
||||||
let file = File::create(path.clone()).unwrap();
|
|
||||||
let fd: RawFd = file.into_raw_fd();
|
|
||||||
let owned_fd = claim_fd(fd).unwrap();
|
|
||||||
let mut file = unsafe { File::from_raw_fd(owned_fd.into_raw_fd()) };
|
|
||||||
file.write_all(b"Hello, World!").unwrap();
|
|
||||||
|
|
||||||
let message = read_to_string(path).unwrap();
|
|
||||||
assert_eq!(message, "Hello, World!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "fd != u32::MAX as RawFd")]
|
#[should_panic(expected = "fd != u32::MAX as RawFd")]
|
||||||
fn test_claim_fd_invalid_neg() {
|
fn test_claim_fd_invalid_neg() {
|
||||||
let fd: RawFd = -1;
|
let _ = claim_fd(-1);
|
||||||
let _ = claim_fd(fd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "fd != u32::MAX as RawFd")]
|
#[should_panic(expected = "fd != u32::MAX as RawFd")]
|
||||||
fn test_claim_fd_invalid_max() {
|
fn test_claim_fd_invalid_max() {
|
||||||
let fd: RawFd = i64::MAX as RawFd;
|
let _ = claim_fd(i64::MAX as RawFd);
|
||||||
let _ = claim_fd(fd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_nullfd_write() {
|
#[should_panic]
|
||||||
let nullfd = open_nullfd().unwrap();
|
fn test_claim_fd_inplace_invalid_neg() {
|
||||||
let mut file = unsafe { File::from_raw_fd(nullfd.into_raw_fd()) };
|
let _ = claim_fd_inplace(-1);
|
||||||
let res = file.write_all(b"Hello, World!");
|
|
||||||
assert!(res.is_err());
|
|
||||||
assert_eq!(
|
|
||||||
res.unwrap_err().to_string(),
|
|
||||||
"Bad file descriptor (os error 9)"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_nullfd_read() {
|
#[should_panic]
|
||||||
|
fn test_claim_fd_inplace_invalid_max() {
|
||||||
|
let _ = claim_fd_inplace(i64::MAX as RawFd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_mask_fd_invalid_neg() {
|
||||||
|
let _ = mask_fd(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_mask_fd_invalid_max() {
|
||||||
|
let _ = mask_fd(i64::MAX as RawFd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_nullfd() -> anyhow::Result<()> {
|
||||||
|
let mut file = FdIo(open_nullfd()?);
|
||||||
|
let mut buf = [0; 10];
|
||||||
|
assert!(matches!(file.read(&mut buf), Ok(0) | Err(_)));
|
||||||
|
assert!(matches!(file.write(&buf), Err(_)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nullfd_read_write() {
|
||||||
let nullfd = open_nullfd().unwrap();
|
let nullfd = open_nullfd().unwrap();
|
||||||
let mut file = unsafe { File::from_raw_fd(nullfd.into_raw_fd()) };
|
let mut buf = vec![0u8; 16];
|
||||||
let mut buffer = [0; 10];
|
assert_eq!(rustix::io::read(&nullfd, &mut buf).unwrap(), 0);
|
||||||
let res = file.read_exact(&mut buffer);
|
assert!(rustix::io::write(&nullfd, b"test").is_err());
|
||||||
assert!(res.is_err());
|
|
||||||
assert_eq!(res.unwrap_err().to_string(), "failed to fill whole buffer");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
269
util/src/file.rs
269
util/src/file.rs
@@ -1,15 +1,45 @@
|
|||||||
|
//! Helpers for working with files
|
||||||
|
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::{fs::OpenOptions, path::Path};
|
use std::{fs::OpenOptions, path::Path};
|
||||||
|
|
||||||
|
/// Level of secrecy applied for a file
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
|
/// The file might contain a public key
|
||||||
Public,
|
Public,
|
||||||
|
/// The file might contain a secret key
|
||||||
Secret,
|
Secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a file writable
|
/// Open a file writeably, truncating the file.
|
||||||
|
///
|
||||||
|
/// Sensible default permissions are chosen based on the value of `visibility`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io::{Write, Read};
|
||||||
|
/// use tempfile::tempdir;
|
||||||
|
/// use rosenpass_util::file::{fopen_r, fopen_w, Visibility};
|
||||||
|
///
|
||||||
|
/// const CONTENTS : &[u8] = b"Hello World";
|
||||||
|
///
|
||||||
|
/// let dir = tempdir()?;
|
||||||
|
/// let path = dir.path().join("secret_key");
|
||||||
|
///
|
||||||
|
/// let mut f = fopen_w(&path, Visibility::Secret)?;
|
||||||
|
/// f.write_all(CONTENTS)?;
|
||||||
|
///
|
||||||
|
/// let mut f = fopen_r(&path)?;
|
||||||
|
/// let mut b = Vec::new();
|
||||||
|
/// f.read_to_end(&mut b)?;
|
||||||
|
/// assert_eq!(CONTENTS, &b);
|
||||||
|
///
|
||||||
|
/// Ok::<(), std::io::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn fopen_w<P: AsRef<Path>>(path: P, visibility: Visibility) -> std::io::Result<File> {
|
pub fn fopen_w<P: AsRef<Path>>(path: P, visibility: Visibility) -> std::io::Result<File> {
|
||||||
let mut options = OpenOptions::new();
|
let mut options = OpenOptions::new();
|
||||||
options.create(true).write(true).read(false).truncate(true);
|
options.create(true).write(true).read(false).truncate(true);
|
||||||
@@ -19,7 +49,12 @@ pub fn fopen_w<P: AsRef<Path>>(path: P, visibility: Visibility) -> std::io::Resu
|
|||||||
};
|
};
|
||||||
options.open(path)
|
options.open(path)
|
||||||
}
|
}
|
||||||
/// Open a file readable
|
|
||||||
|
/// Open a file readably
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [fopen_w].
|
||||||
pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
@@ -29,9 +64,47 @@ pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
|||||||
.open(path)
|
.open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [std::io::Read] adding [read_slice_to_end]
|
||||||
pub trait ReadSliceToEnd {
|
pub trait ReadSliceToEnd {
|
||||||
|
/// Error type returned by functions in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Read slice asserting that the length of the data to read is at most
|
||||||
|
/// as long as the buffer to read into
|
||||||
|
///
|
||||||
|
/// Note that this *may* append data read to [buf] even if the function fails,
|
||||||
|
/// so the caller should make no assumptions about the contents of the buffer
|
||||||
|
/// after calling read_slice_to_end if the result is an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::file::ReadSliceToEnd;
|
||||||
|
///
|
||||||
|
/// const DATA : &[u8] = b"Hello World";
|
||||||
|
///
|
||||||
|
/// // It is OK if file and buffer are equally long
|
||||||
|
/// let mut buf = vec![b' '; 11];
|
||||||
|
/// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf[..DATA.len()]);
|
||||||
|
/// assert!(res.is_ok()); // Read is overlong
|
||||||
|
/// assert_eq!(buf, DATA); // Finally, data was successfully read
|
||||||
|
///
|
||||||
|
/// // It is OK if the buffer is longer than the file
|
||||||
|
/// let mut buf = vec![b' '; 16];
|
||||||
|
/// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf);
|
||||||
|
/// assert!(matches!(res, Ok(11)));
|
||||||
|
/// assert_eq!(buf, b"Hello World "); // Data was still read to the buffer!
|
||||||
|
///
|
||||||
|
/// // It is not OK if the buffer is shorter than the file
|
||||||
|
/// let mut buf = vec![b' '; 5];
|
||||||
|
/// let res = Clone::clone(&DATA).read_slice_to_end(&mut buf);
|
||||||
|
/// assert!(res.is_err());
|
||||||
|
///
|
||||||
|
/// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED
|
||||||
|
/// assert_eq!(buf, b"Hello"); // Data was still read to the buffer!
|
||||||
|
///
|
||||||
|
/// Ok::<(), std::io::Error>(())
|
||||||
|
/// ```
|
||||||
fn read_slice_to_end(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
|
fn read_slice_to_end(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +126,50 @@ impl<R: Read> ReadSliceToEnd for R {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [std::io::Read] adding [read_exact_to_end]
|
||||||
pub trait ReadExactToEnd {
|
pub trait ReadExactToEnd {
|
||||||
|
/// Error type returned by functions in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Read slice asserting that the length of the data to be read
|
||||||
|
/// and the buffer are exactly the same length.
|
||||||
|
///
|
||||||
|
/// Note that this *may* append data read to [buf] even if the function fails,
|
||||||
|
/// so the caller should make no assumptions about the contents of the buffer
|
||||||
|
/// after calling read_exact_to_end if the result is an error.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::file::ReadExactToEnd;
|
||||||
|
///
|
||||||
|
/// const DATA : &[u8] = b"Hello World";
|
||||||
|
///
|
||||||
|
/// // It is OK if file and buffer are equally long
|
||||||
|
/// let mut buf = vec![b' '; 11];
|
||||||
|
/// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf[..DATA.len()]);
|
||||||
|
/// assert!(res.is_ok()); // Read is overlong
|
||||||
|
/// assert_eq!(buf, DATA); // Finally, data was successfully read
|
||||||
|
///
|
||||||
|
/// // It is not OK if the buffer is longer than the file
|
||||||
|
/// let mut buf = vec![b' '; 16];
|
||||||
|
/// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf);
|
||||||
|
/// assert!(res.is_err());
|
||||||
|
///
|
||||||
|
/// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED
|
||||||
|
/// // The read implementation for &[u8] happens not to do this
|
||||||
|
/// assert_eq!(buf, b" "); // Data was still read to the buffer!
|
||||||
|
///
|
||||||
|
/// // It is not OK if the buffer is shorter than the file
|
||||||
|
/// let mut buf = vec![b' '; 5];
|
||||||
|
/// let res = Clone::clone(&DATA).read_exact_to_end(&mut buf);
|
||||||
|
/// assert!(res.is_err());
|
||||||
|
///
|
||||||
|
/// // THE BUFFER MAY STILL BE FILLED THOUGH, BUT THIS IS NOT GUARANTEED
|
||||||
|
/// assert_eq!(buf, b"Hello"); // Data was still read to the buffer!
|
||||||
|
///
|
||||||
|
/// Ok::<(), std::io::Error>(())
|
||||||
|
/// ```
|
||||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
|
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,51 +184,190 @@ impl<R: Read> ReadExactToEnd for R {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load a value from a file
|
||||||
pub trait LoadValue {
|
pub trait LoadValue {
|
||||||
|
/// Error type returned
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Load a value from a file
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::path::Path;
|
||||||
|
/// use std::io::Write;
|
||||||
|
/// use tempfile::tempdir;
|
||||||
|
/// use rosenpass_util::file::{fopen_r, fopen_w, LoadValue, ReadExactToEnd, StoreValue, Visibility};
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, PartialEq, Eq)]
|
||||||
|
/// struct MyInt(pub u32);
|
||||||
|
///
|
||||||
|
/// impl StoreValue for MyInt {
|
||||||
|
/// type Error = std::io::Error;
|
||||||
|
///
|
||||||
|
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||||
|
/// let mut f = fopen_w(path, Visibility::Public)?;
|
||||||
|
/// f.write_all(&self.0.to_le_bytes())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl LoadValue for MyInt {
|
||||||
|
/// type Error = anyhow::Error;
|
||||||
|
///
|
||||||
|
/// fn load<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
|
/// where
|
||||||
|
/// Self: Sized,
|
||||||
|
/// {
|
||||||
|
/// let mut b = [0u8; 4];
|
||||||
|
/// fopen_r(path)?.read_exact_to_end(&mut b)?;
|
||||||
|
/// Ok(MyInt(u32::from_le_bytes(b)))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let dir = tempdir()?;
|
||||||
|
/// let path = dir.path().join("my_int");
|
||||||
|
///
|
||||||
|
/// let orig = MyInt(17);
|
||||||
|
/// orig.store(&path)?;
|
||||||
|
///
|
||||||
|
/// let copy = MyInt::load(&path)?;
|
||||||
|
/// assert_eq!(orig, copy);
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
fn load<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load a value from a file encoded as base64
|
||||||
pub trait LoadValueB64 {
|
pub trait LoadValueB64 {
|
||||||
|
/// Error type returned
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Load a value from a file encoded as base64
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::path::Path;
|
||||||
|
/// use tempfile::tempdir;
|
||||||
|
/// use rosenpass_util::b64::{b64_decode, b64_encode};
|
||||||
|
/// use rosenpass_util::file::{
|
||||||
|
/// fopen_r, fopen_w, LoadValueB64, ReadSliceToEnd, StoreValueB64, StoreValueB64Writer,
|
||||||
|
/// Visibility,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, PartialEq, Eq)]
|
||||||
|
/// struct MyInt(pub u32);
|
||||||
|
///
|
||||||
|
/// impl StoreValueB64Writer for MyInt {
|
||||||
|
/// type Error = anyhow::Error;
|
||||||
|
///
|
||||||
|
/// fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
|
/// &self,
|
||||||
|
/// mut writer: W,
|
||||||
|
/// ) -> Result<(), Self::Error> {
|
||||||
|
/// // Let me just point out while writing this example,
|
||||||
|
/// // that this API is currently, entirely shit in terms of
|
||||||
|
/// // how it deals with buffer lengths.
|
||||||
|
/// let mut buf = [0u8; F];
|
||||||
|
/// let b64 = b64_encode(&self.0.to_le_bytes(), &mut buf)?;
|
||||||
|
/// writer.write_all(b64.as_bytes())?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl StoreValueB64 for MyInt {
|
||||||
|
/// type Error = anyhow::Error;
|
||||||
|
///
|
||||||
|
/// fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>
|
||||||
|
/// where
|
||||||
|
/// Self: Sized,
|
||||||
|
/// {
|
||||||
|
/// // The buffer length (first generic arg) is kind of an upper bound
|
||||||
|
/// self.store_b64_writer::<F, _>(fopen_w(path, Visibility::Public)?)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl LoadValueB64 for MyInt {
|
||||||
|
/// type Error = anyhow::Error;
|
||||||
|
///
|
||||||
|
/// fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
|
/// where
|
||||||
|
/// Self: Sized,
|
||||||
|
/// {
|
||||||
|
/// // The buffer length is kind of an upper bound
|
||||||
|
/// let mut b64_buf = [0u8; F];
|
||||||
|
/// let b64_len = fopen_r(path)?.read_slice_to_end(&mut b64_buf)?;
|
||||||
|
/// let b64_dat = &b64_buf[..b64_len];
|
||||||
|
///
|
||||||
|
/// let mut buf = [0u8; 4];
|
||||||
|
/// b64_decode(b64_dat, &mut buf)?;
|
||||||
|
/// Ok(MyInt(u32::from_le_bytes(buf)))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let dir = tempdir()?;
|
||||||
|
/// let path = dir.path().join("my_int");
|
||||||
|
///
|
||||||
|
/// let orig = MyInt(17);
|
||||||
|
/// orig.store_b64::<10, _>(&path)?;
|
||||||
|
///
|
||||||
|
/// let copy = MyInt::load_b64::<10, _>(&path)?;
|
||||||
|
/// assert_eq!(orig, copy);
|
||||||
|
///
|
||||||
|
/// Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store a value encoded as base64 in a file.
|
||||||
pub trait StoreValueB64 {
|
pub trait StoreValueB64 {
|
||||||
|
/// Error type returned
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Store a value encoded as base64 in a file.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [LoadValueB64::load_b64].
|
||||||
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>
|
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store a value encoded as base64 to a writable stream
|
||||||
pub trait StoreValueB64Writer {
|
pub trait StoreValueB64Writer {
|
||||||
|
/// Error type returned
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Store a value encoded as base64 to a writable stream
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [LoadValueB64::load_b64].
|
||||||
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
fn store_b64_writer<const F: usize, W: std::io::Write>(
|
||||||
&self,
|
&self,
|
||||||
writer: W,
|
writer: W,
|
||||||
) -> Result<(), Self::Error>;
|
) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store a value in a file
|
||||||
pub trait StoreValue {
|
pub trait StoreValue {
|
||||||
|
/// Error type returned
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Store a value in a file
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [LoadValue::load].
|
||||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DisplayValueB64 {
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn display_b64<'o>(&self, output: &'o mut [u8]) -> Result<&'o str, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,87 +1,260 @@
|
|||||||
pub fn mutating<T, F>(mut v: T, f: F) -> T
|
//! Syntax sugar & helpers for a functional programming style and method chains
|
||||||
|
|
||||||
|
/// Mutate a value; mostly syntactic sugar
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::borrow::Borrow;
|
||||||
|
/// use rosenpass_util::functional::{mutating, MutatingExt, sideeffect, SideffectExt, ApplyExt};
|
||||||
|
/// use rosenpass_util::mem::DiscardResultExt;
|
||||||
|
///
|
||||||
|
/// // Say you have a function that takes a mutable reference
|
||||||
|
/// fn replace<T: Copy + Eq>(slice: &mut [T], targ: T, by: T) {
|
||||||
|
/// for val in slice.iter_mut() {
|
||||||
|
/// if *val == targ {
|
||||||
|
/// *val = by;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Or you have some action that you want to perform as a side effect
|
||||||
|
/// fn count<T: Copy + Eq>(accumulator: &mut usize, slice: &[T], targ: T) {
|
||||||
|
/// *accumulator += slice.iter()
|
||||||
|
/// .filter(|e| *e == &targ)
|
||||||
|
/// .count();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Lets say, you also have a function that actually modifies the value
|
||||||
|
/// fn rot2<const N : usize>(slice: [u8; N]) -> [u8; N] {
|
||||||
|
/// let it = slice.iter()
|
||||||
|
/// .cycle()
|
||||||
|
/// .skip(2)
|
||||||
|
/// .take(N);
|
||||||
|
///
|
||||||
|
/// let mut ret = [0u8; N];
|
||||||
|
/// for (no, elm) in it.enumerate() {
|
||||||
|
/// ret[no] = *elm;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// ret
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Then these function are kind of clunky to use in an expression;
|
||||||
|
/// // it can be done, but the resulting code is a bit verbose
|
||||||
|
/// let mut accu = 0;
|
||||||
|
/// assert_eq!(b"llo_WorldHe", &{
|
||||||
|
/// let mut buf = b"Hello World".to_owned();
|
||||||
|
/// count(&mut accu, &buf, b'l');
|
||||||
|
/// replace(&mut buf, b' ', b'_');
|
||||||
|
/// rot2(buf)
|
||||||
|
/// });
|
||||||
|
/// assert_eq!(accu, 3);
|
||||||
|
///
|
||||||
|
/// // Instead you could use mutating for a slightly prettier syntax,
|
||||||
|
/// // but this makes only sense if you want to apply a single action
|
||||||
|
/// assert_eq!(b"Hello_World",
|
||||||
|
/// &mutating(b"Hello World".to_owned(), |buf|
|
||||||
|
/// replace(buf, b' ', b'_')));
|
||||||
|
///
|
||||||
|
/// // The same is the case for sideeffect()
|
||||||
|
/// assert_eq!(b"Hello World",
|
||||||
|
/// &sideeffect(b"Hello World".to_owned(), |buf|
|
||||||
|
/// count(&mut accu, buf, b'l')));
|
||||||
|
/// assert_eq!(accu, 6);
|
||||||
|
///
|
||||||
|
/// // Calling rot2 on its own is straightforward of course
|
||||||
|
/// assert_eq!(b"llo WorldHe", &rot2(b"Hello World".to_owned()));
|
||||||
|
///
|
||||||
|
/// // These operations can be conveniently used in a method chain
|
||||||
|
/// // by using the extension traits.
|
||||||
|
/// //
|
||||||
|
/// // This is also quite handy if you just need to
|
||||||
|
/// // modify a value in a long method chain.
|
||||||
|
/// //
|
||||||
|
/// // Here apply() also comes in quite handy, because we can use it
|
||||||
|
/// // to modify the value itself (turning it into a reference).
|
||||||
|
/// assert_eq!(b"llo_WorldHe",
|
||||||
|
/// b"Hello World"
|
||||||
|
/// .to_owned()
|
||||||
|
/// .sideeffect(|buf| count(&mut accu, buf, b'l'))
|
||||||
|
/// .mutating(|buf| replace(buf, b' ', b'_'))
|
||||||
|
/// .apply(rot2)
|
||||||
|
/// .borrow() as &[u8]);
|
||||||
|
/// assert_eq!(accu, 9);
|
||||||
|
///
|
||||||
|
/// // There is also the mutating_mut variant, which can operate on any mutable reference;
|
||||||
|
/// // this is mainly useful in a method chain if you are dealing with a mutable reference.
|
||||||
|
/// //
|
||||||
|
/// // This example is quite artificial though.
|
||||||
|
/// assert_eq!(b"llo_WorldHe",
|
||||||
|
/// b"hello world"
|
||||||
|
/// .to_owned()
|
||||||
|
/// .mutating(|buf|
|
||||||
|
/// // Can not use sideeffect_ref at the start, because it drops the mut reference
|
||||||
|
/// // status
|
||||||
|
/// buf.sideeffect_mut(|buf| count(&mut accu, buf, b'l'))
|
||||||
|
/// .mutating_mut(|buf| replace(buf, b' ', b'_'))
|
||||||
|
/// .mutating_mut(|buf| replace(buf, b'h', b'H'))
|
||||||
|
/// .mutating_mut(|buf| replace(buf, b'w', b'W'))
|
||||||
|
/// // Using rot2 is more complex now
|
||||||
|
/// .mutating_mut(|buf| {
|
||||||
|
/// *buf = rot2(*buf);
|
||||||
|
/// })
|
||||||
|
/// // Can use sideeffect_ref at the end, because we no longer need
|
||||||
|
/// // the &mut reference
|
||||||
|
/// .sideeffect_ref(|buf| count(&mut accu, *buf, b'l'))
|
||||||
|
/// // And we can use apply to fix the return value – if we really want to go
|
||||||
|
/// // crazy and avoid using a {} block
|
||||||
|
/// .apply(|_| ())
|
||||||
|
/// // [crate::mem::DiscardResult::discard_result] does the same job and it is more explicit.
|
||||||
|
/// .discard_result())
|
||||||
|
/// .borrow() as &[u8]);
|
||||||
|
/// assert_eq!(accu, 15);
|
||||||
|
/// ```
|
||||||
|
pub fn mutating<T, F>(mut v: T, mut f: F) -> T
|
||||||
where
|
where
|
||||||
F: Fn(&mut T),
|
F: FnMut(&mut T),
|
||||||
{
|
{
|
||||||
f(&mut v);
|
f(&mut v);
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutating values on the fly in a method chain
|
||||||
pub trait MutatingExt {
|
pub trait MutatingExt {
|
||||||
|
/// Mutating values on the fly in a method chain (owning)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn mutating<F>(self, f: F) -> Self
|
fn mutating<F>(self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut Self);
|
F: FnMut(&mut Self);
|
||||||
|
|
||||||
|
/// Mutating values on the fly in a method chain (non-owning)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
|
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut Self);
|
F: FnMut(&mut Self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> MutatingExt for T {
|
impl<T> MutatingExt for T {
|
||||||
fn mutating<F>(self, f: F) -> Self
|
fn mutating<F>(self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut Self),
|
F: FnMut(&mut Self),
|
||||||
{
|
{
|
||||||
mutating(self, f)
|
mutating(self, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
|
fn mutating_mut<F>(&mut self, mut f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut Self),
|
F: FnMut(&mut Self),
|
||||||
{
|
{
|
||||||
f(self);
|
f(self);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sideeffect<T, F>(v: T, f: F) -> T
|
/// Apply a sideeffect using some value in an expression
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
|
pub fn sideeffect<T, F>(v: T, mut f: F) -> T
|
||||||
where
|
where
|
||||||
F: Fn(&T),
|
F: FnMut(&T),
|
||||||
{
|
{
|
||||||
f(&v);
|
f(&v);
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply sideeffect on the fly in a method chain
|
||||||
pub trait SideffectExt {
|
pub trait SideffectExt {
|
||||||
|
/// Apply sideeffect on the fly in a method chain (owning)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn sideeffect<F>(self, f: F) -> Self
|
fn sideeffect<F>(self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self);
|
F: FnMut(&Self);
|
||||||
|
/// Apply sideeffect on the fly in a method chain (immutable ref)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn sideeffect_ref<F>(&self, f: F) -> &Self
|
fn sideeffect_ref<F>(&self, f: F) -> &Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self);
|
F: FnMut(&Self);
|
||||||
|
/// Apply sideeffect on the fly in a method chain (mutable ref)
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
|
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self);
|
F: FnMut(&Self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SideffectExt for T {
|
impl<T> SideffectExt for T {
|
||||||
fn sideeffect<F>(self, f: F) -> Self
|
fn sideeffect<F>(self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self),
|
F: FnMut(&Self),
|
||||||
{
|
{
|
||||||
sideeffect(self, f)
|
sideeffect(self, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sideeffect_ref<F>(&self, f: F) -> &Self
|
fn sideeffect_ref<F>(&self, mut f: F) -> &Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self),
|
F: FnMut(&Self),
|
||||||
{
|
{
|
||||||
f(self);
|
f(self);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
|
fn sideeffect_mut<F>(&mut self, mut f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(&Self),
|
F: FnMut(&Self),
|
||||||
{
|
{
|
||||||
f(self);
|
f(self);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Just run the function
|
||||||
|
///
|
||||||
|
/// This is occasionally useful; in particular, you can
|
||||||
|
/// use it to control the meaning of the question mark operator.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::functional::run;
|
||||||
|
///
|
||||||
|
/// fn add_and_mul(a: Option<u32>, b: Option<u32>, c: anyhow::Result<u32>, d: anyhow::Result<u32>) -> u32 {
|
||||||
|
/// run(|| -> anyhow::Result<u32> {
|
||||||
|
/// let ab = run(|| Some(a? * b?)).unwrap_or(0);
|
||||||
|
/// Ok(ab + c? + d?)
|
||||||
|
/// }).unwrap()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(98, add_and_mul(Some(10), Some(9), Ok(3), Ok(5)));
|
||||||
|
/// assert_eq!(8, add_and_mul(None, Some(15), Ok(3), Ok(5)));
|
||||||
|
/// ```
|
||||||
pub fn run<R, F: FnOnce() -> R>(f: F) -> R {
|
pub fn run<R, F: FnOnce() -> R>(f: F) -> R {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a function to a value in a method chain
|
||||||
pub trait ApplyExt: Sized {
|
pub trait ApplyExt: Sized {
|
||||||
|
/// Apply a function to a value in a method chain
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [mutating].
|
||||||
fn apply<R, F>(self, f: F) -> R
|
fn apply<R, F>(self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(Self) -> R;
|
F: FnOnce(Self) -> R;
|
||||||
|
|||||||
355
util/src/io.rs
355
util/src/io.rs
@@ -1,8 +1,262 @@
|
|||||||
|
//! Helpers for performing IO
|
||||||
|
//!
|
||||||
|
//! # IO Error handling helpers tutorial
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use std::io::ErrorKind as EK;
|
||||||
|
//!
|
||||||
|
//! // It can be a bit hard to use IO errors in match statements
|
||||||
|
//!
|
||||||
|
//! fn io_placeholder() -> std::io::Result<()> {
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! loop {
|
||||||
|
//! match io_placeholder() {
|
||||||
|
//! Ok(()) => break,
|
||||||
|
//! // All errors are unreachable; just here for demo purposes
|
||||||
|
//! Err(e) if e.kind() == EK::Interrupted => continue,
|
||||||
|
//! Err(e) if e.kind() == EK::WouldBlock => {
|
||||||
|
//! panic!("This particular function is not designed to be used in nonblocking code!");
|
||||||
|
//! }
|
||||||
|
//! Err(e) => Err(e)?,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // For this reason this module contains various helper functions to make
|
||||||
|
//! // matching on error kinds a bit less repetitive. [IoResultKindHintExt::io_err_kind_hint]
|
||||||
|
//! // provides the basic functionality for use mostly with std::io::Result
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::IoResultKindHintExt;
|
||||||
|
//!
|
||||||
|
//! loop {
|
||||||
|
//! match io_placeholder().io_err_kind_hint() {
|
||||||
|
//! Ok(()) => break,
|
||||||
|
//! // All errors are unreachable; just here for demo purposes
|
||||||
|
//! Err((_, EK::Interrupted)) => continue,
|
||||||
|
//! Err((_, EK::WouldBlock)) => {
|
||||||
|
//! // Unreachable, just here for explanation purposes
|
||||||
|
//! panic!("This particular function is not designed to be used in nonblocking code!");
|
||||||
|
//! }
|
||||||
|
//! Err((e, _)) => Err(e)?,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // The trait can be customized; firstly, you can use IoErrorKind
|
||||||
|
//! // for error types that can be fully represented as std::io::ErrorKind
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::IoErrorKind;
|
||||||
|
//!
|
||||||
|
//! #[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||||
|
//! enum MyErrno {
|
||||||
|
//! #[error("Got interrupted")]
|
||||||
|
//! Interrupted,
|
||||||
|
//! #[error("In nonblocking mode")]
|
||||||
|
//! WouldBlock,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl IoErrorKind for MyErrno {
|
||||||
|
//! fn io_error_kind(&self) -> std::io::ErrorKind {
|
||||||
|
//! use MyErrno as ME;
|
||||||
|
//! match self {
|
||||||
|
//! ME::Interrupted => EK::Interrupted,
|
||||||
|
//! ME::WouldBlock => EK::WouldBlock,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! EK::Interrupted,
|
||||||
|
//! std::io::Error::new(EK::Interrupted, "artificially interrupted").io_error_kind()
|
||||||
|
//! );
|
||||||
|
//! assert_eq!(EK::Interrupted, MyErrno::Interrupted.io_error_kind());
|
||||||
|
//! assert_eq!(EK::WouldBlock, MyErrno::WouldBlock.io_error_kind());
|
||||||
|
//!
|
||||||
|
//! // And when an error can not fully be represented as an std::io::ErrorKind,
|
||||||
|
//! // you can still use [TryIoErrorKind]
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::TryIoErrorKind;
|
||||||
|
//!
|
||||||
|
//! #[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||||
|
//! enum MyErrnoOrBlue {
|
||||||
|
//! #[error("Got interrupted")]
|
||||||
|
//! Interrupted,
|
||||||
|
//! #[error("In nonblocking mode")]
|
||||||
|
//! WouldBlock,
|
||||||
|
//! #[error("I am feeling blue")]
|
||||||
|
//! FeelingBlue,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl TryIoErrorKind for MyErrnoOrBlue {
|
||||||
|
//! fn try_io_error_kind(&self) -> Option<std::io::ErrorKind> {
|
||||||
|
//! use MyErrnoOrBlue as ME;
|
||||||
|
//! match self {
|
||||||
|
//! ME::Interrupted => Some(EK::Interrupted),
|
||||||
|
//! ME::WouldBlock => Some(EK::WouldBlock),
|
||||||
|
//! ME::FeelingBlue => None,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Some(EK::Interrupted),
|
||||||
|
//! MyErrnoOrBlue::Interrupted.try_io_error_kind()
|
||||||
|
//! );
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Some(EK::WouldBlock),
|
||||||
|
//! MyErrnoOrBlue::WouldBlock.try_io_error_kind()
|
||||||
|
//! );
|
||||||
|
//! assert_eq!(None, MyErrnoOrBlue::FeelingBlue.try_io_error_kind());
|
||||||
|
//!
|
||||||
|
//! // TryIoErrorKind is automatically implemented for all types that implement
|
||||||
|
//! // IoErrorKind
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Some(EK::Interrupted),
|
||||||
|
//! std::io::Error::new(EK::Interrupted, "artificially interrupted").try_io_error_kind()
|
||||||
|
//! );
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Some(EK::Interrupted),
|
||||||
|
//! MyErrno::Interrupted.try_io_error_kind()
|
||||||
|
//! );
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Some(EK::WouldBlock),
|
||||||
|
//! MyErrno::WouldBlock.try_io_error_kind()
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // By implementing IoErrorKind, we can automatically make use of IoResultKindHintExt<T>
|
||||||
|
//! // with our custom error type
|
||||||
|
//!
|
||||||
|
//! //use rosenpass_util::io::IoResultKindHintExt;
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Ok::<_, MyErrno>(42).io_err_kind_hint(),
|
||||||
|
//! Ok(42));
|
||||||
|
//! assert!(matches!(
|
||||||
|
//! Err::<(), _>(std::io::Error::new(EK::Interrupted, "artificially interrupted")).io_err_kind_hint(),
|
||||||
|
//! Err((err, EK::Interrupted)) if format!("{err:?}") == "Custom { kind: Interrupted, error: \"artificially interrupted\" }"));
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Err::<(), _>(MyErrno::Interrupted).io_err_kind_hint(),
|
||||||
|
//! Err((MyErrno::Interrupted, EK::Interrupted)));
|
||||||
|
//!
|
||||||
|
//! // Correspondingly, TryIoResultKindHintExt can be used for Results with Errors
|
||||||
|
//! // that implement TryIoErrorKind
|
||||||
|
//!
|
||||||
|
//! use crate::rosenpass_util::io::TryIoResultKindHintExt;
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Ok::<_, MyErrnoOrBlue>(42).try_io_err_kind_hint(),
|
||||||
|
//! Ok(42));
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Err::<(), _>(MyErrnoOrBlue::Interrupted).try_io_err_kind_hint(),
|
||||||
|
//! Err((MyErrnoOrBlue::Interrupted, Some(EK::Interrupted))));
|
||||||
|
//! assert_eq!(
|
||||||
|
//! Err::<(), _>(MyErrnoOrBlue::FeelingBlue).try_io_err_kind_hint(),
|
||||||
|
//! Err((MyErrnoOrBlue::FeelingBlue, None)));
|
||||||
|
//!
|
||||||
|
//! // SubstituteForIoErrorKindExt serves as a helper to handle specific ErrorKinds
|
||||||
|
//! // using a method chaining style. It works on anything that implements TryIoErrorKind.
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::SubstituteForIoErrorKindExt;
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Ok(42),
|
||||||
|
//! Err(MyErrnoOrBlue::Interrupted)
|
||||||
|
//! .substitute_for_ioerr_kind_with(EK::Interrupted, || 42));
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock),
|
||||||
|
//! Err(MyErrnoOrBlue::WouldBlock)
|
||||||
|
//! .substitute_for_ioerr_kind_with(EK::Interrupted, || 42));
|
||||||
|
//!
|
||||||
|
//! // The other functions in SubstituteForIoErrorKindExt are mostly just wrappers,
|
||||||
|
//! // getting the same job done with minor convenience
|
||||||
|
//!
|
||||||
|
//! // Plain Ok() value instead of function
|
||||||
|
//! assert_eq!(Ok(42),
|
||||||
|
//! Err(MyErrnoOrBlue::Interrupted)
|
||||||
|
//! .substitute_for_ioerr_kind(EK::Interrupted, 42));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock),
|
||||||
|
//! Err(MyErrnoOrBlue::WouldBlock)
|
||||||
|
//! .substitute_for_ioerr_kind(EK::Interrupted, 42));
|
||||||
|
//!
|
||||||
|
//! // For specific errors
|
||||||
|
//! assert_eq!(Ok(42),
|
||||||
|
//! Err(MyErrnoOrBlue::Interrupted)
|
||||||
|
//! .substitute_for_ioerr_interrupted_with(|| 42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock_with(|| 23));
|
||||||
|
//! assert_eq!(Ok(23),
|
||||||
|
//! Err(MyErrnoOrBlue::WouldBlock)
|
||||||
|
//! .substitute_for_ioerr_interrupted_with(|| 42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock_with(|| 23));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue),
|
||||||
|
//! Err(MyErrnoOrBlue::FeelingBlue)
|
||||||
|
//! .substitute_for_ioerr_interrupted_with(|| 42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock_with(|| 23));
|
||||||
|
//!
|
||||||
|
//! // And for specific errors without the function call
|
||||||
|
//! assert_eq!(Ok(42),
|
||||||
|
//! Err(MyErrnoOrBlue::Interrupted)
|
||||||
|
//! .substitute_for_ioerr_interrupted(42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock(23));
|
||||||
|
//! assert_eq!(Ok(23),
|
||||||
|
//! Err(MyErrnoOrBlue::WouldBlock)
|
||||||
|
//! .substitute_for_ioerr_interrupted(42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock(23));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue),
|
||||||
|
//! Err(MyErrnoOrBlue::FeelingBlue)
|
||||||
|
//! .substitute_for_ioerr_interrupted(42)
|
||||||
|
//! .substitute_for_ioerr_wouldblock(23));
|
||||||
|
//!
|
||||||
|
//! // handle_interrupted automates the process of handling ErrorKind::Interrupted
|
||||||
|
//! // in cases where the action should simply be rerun; it can handle any error type
|
||||||
|
//! // that implements TryIoErrorKind. It lets other errors and Ok(_) pass through.
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::handle_interrupted;
|
||||||
|
//!
|
||||||
|
//! let mut ctr = 0u32;
|
||||||
|
//! let mut simulate_io = || -> Result<u32, MyErrnoOrBlue> {
|
||||||
|
//! let r = match ctr % 6 {
|
||||||
|
//! 1 => Ok(42),
|
||||||
|
//! 3 => Err(MyErrnoOrBlue::FeelingBlue),
|
||||||
|
//! 5 => Err(MyErrnoOrBlue::WouldBlock),
|
||||||
|
//! _ => Err(MyErrnoOrBlue::Interrupted),
|
||||||
|
//! };
|
||||||
|
//! ctr += 1;
|
||||||
|
//! r
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Ok(Some(42)), handle_interrupted(&mut simulate_io));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), handle_interrupted(&mut simulate_io));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::WouldBlock), handle_interrupted(&mut simulate_io));
|
||||||
|
//! // never returns None
|
||||||
|
//!
|
||||||
|
//! // nonblocking_handle_io_errors performs the same job, except that
|
||||||
|
//! // WouldBlock is substituted with Ok(None)
|
||||||
|
//!
|
||||||
|
//! use rosenpass_util::io::nonblocking_handle_io_errors;
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Ok(Some(42)), nonblocking_handle_io_errors(&mut simulate_io));
|
||||||
|
//! assert_eq!(Err(MyErrnoOrBlue::FeelingBlue), nonblocking_handle_io_errors(&mut simulate_io));
|
||||||
|
//! assert_eq!(Ok(None), nonblocking_handle_io_errors(&mut simulate_io));
|
||||||
|
//!
|
||||||
|
//! Ok::<_, anyhow::Error>(())
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::{borrow::Borrow, io};
|
use std::{borrow::Borrow, io};
|
||||||
|
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
|
use zerocopy::AsBytes;
|
||||||
|
|
||||||
|
/// Generic trait for accessing [std::io::Error::kind]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub trait IoErrorKind {
|
pub trait IoErrorKind {
|
||||||
|
/// Conversion to [std::io::Error::kind]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn io_error_kind(&self) -> io::ErrorKind;
|
fn io_error_kind(&self) -> io::ErrorKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,7 +266,17 @@ impl<T: Borrow<io::Error>> IoErrorKind for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic trait for accessing [std::io::Error::kind] where it may not be present
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub trait TryIoErrorKind {
|
pub trait TryIoErrorKind {
|
||||||
|
/// Conversion to [std::io::Error::kind] where it may not be present
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn try_io_error_kind(&self) -> Option<io::ErrorKind>;
|
fn try_io_error_kind(&self) -> Option<io::ErrorKind>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,8 +286,19 @@ impl<T: IoErrorKind> TryIoErrorKind for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for accessing [std::io::Error::kind] in Results
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub trait IoResultKindHintExt<T>: Sized {
|
pub trait IoResultKindHintExt<T>: Sized {
|
||||||
|
/// Error type including the ErrorKind hint
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Helper for accessing [std::io::Error::kind] in Results
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn io_err_kind_hint(self) -> Result<T, (Self::Error, io::ErrorKind)>;
|
fn io_err_kind_hint(self) -> Result<T, (Self::Error, io::ErrorKind)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +312,19 @@ impl<T, E: IoErrorKind> IoResultKindHintExt<T> for Result<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for accessing [std::io::Error::kind] in Results where it may not be present
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub trait TryIoResultKindHintExt<T>: Sized {
|
pub trait TryIoResultKindHintExt<T>: Sized {
|
||||||
|
/// Error type including the ErrorKind hint
|
||||||
type Error;
|
type Error;
|
||||||
|
/// Helper for accessing [std::io::Error::kind] in Results where it may not be present
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn try_io_err_kind_hint(self) -> Result<T, (Self::Error, Option<io::ErrorKind>)>;
|
fn try_io_err_kind_hint(self) -> Result<T, (Self::Error, Option<io::ErrorKind>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,17 +338,41 @@ impl<T, E: TryIoErrorKind> TryIoResultKindHintExt<T> for Result<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for working with IO results using a method chaining style
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub trait SubstituteForIoErrorKindExt<T>: Sized {
|
pub trait SubstituteForIoErrorKindExt<T>: Sized {
|
||||||
|
/// Error type produced by methods in this trait
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Substitute errors with a certain [std::io::ErrorKind] by a value produced by a function
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_kind_with<F: FnOnce() -> T>(
|
fn substitute_for_ioerr_kind_with<F: FnOnce() -> T>(
|
||||||
self,
|
self,
|
||||||
kind: io::ErrorKind,
|
kind: io::ErrorKind,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<T, Self::Error>;
|
) -> Result<T, Self::Error>;
|
||||||
|
|
||||||
|
/// Substitute errors with a certain [std::io::ErrorKind] by a value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_kind(self, kind: io::ErrorKind, v: T) -> Result<T, Self::Error> {
|
fn substitute_for_ioerr_kind(self, kind: io::ErrorKind, v: T) -> Result<T, Self::Error> {
|
||||||
self.substitute_for_ioerr_kind_with(kind, || v)
|
self.substitute_for_ioerr_kind_with(kind, || v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::Interrupted] by a value
|
||||||
|
/// produced by a function
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_interrupted_with<F: FnOnce() -> T>(
|
fn substitute_for_ioerr_interrupted_with<F: FnOnce() -> T>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
@@ -70,10 +380,21 @@ pub trait SubstituteForIoErrorKindExt<T>: Sized {
|
|||||||
self.substitute_for_ioerr_kind_with(io::ErrorKind::Interrupted, f)
|
self.substitute_for_ioerr_kind_with(io::ErrorKind::Interrupted, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::Interrupted] by a value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_interrupted(self, v: T) -> Result<T, Self::Error> {
|
fn substitute_for_ioerr_interrupted(self, v: T) -> Result<T, Self::Error> {
|
||||||
self.substitute_for_ioerr_interrupted_with(|| v)
|
self.substitute_for_ioerr_interrupted_with(|| v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::WouldBlock] by a value
|
||||||
|
/// produced by a function
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_wouldblock_with<F: FnOnce() -> T>(
|
fn substitute_for_ioerr_wouldblock_with<F: FnOnce() -> T>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
@@ -81,6 +402,11 @@ pub trait SubstituteForIoErrorKindExt<T>: Sized {
|
|||||||
self.substitute_for_ioerr_kind_with(io::ErrorKind::WouldBlock, f)
|
self.substitute_for_ioerr_kind_with(io::ErrorKind::WouldBlock, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Substitute errors with [std::io::ErrorKind] [std::io::ErrorKind::WouldBlock] by a value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
fn substitute_for_ioerr_wouldblock(self, v: T) -> Result<T, Self::Error> {
|
fn substitute_for_ioerr_wouldblock(self, v: T) -> Result<T, Self::Error> {
|
||||||
self.substitute_for_ioerr_wouldblock_with(|| v)
|
self.substitute_for_ioerr_wouldblock_with(|| v)
|
||||||
}
|
}
|
||||||
@@ -107,6 +433,10 @@ impl<T, E: TryIoErrorKind> SubstituteForIoErrorKindExt<T> for Result<T, E> {
|
|||||||
/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))`
|
/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))`
|
||||||
/// - `Interrupted` is handled internally, by retrying the IO operation
|
/// - `Interrupted` is handled internally, by retrying the IO operation
|
||||||
/// - Other errors are returned as is
|
/// - Other errors are returned as is
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub fn handle_interrupted<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
|
pub fn handle_interrupted<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
|
||||||
where
|
where
|
||||||
E: TryIoErrorKind,
|
E: TryIoErrorKind,
|
||||||
@@ -128,6 +458,10 @@ where
|
|||||||
/// - `Interrupted` is handled internally, by retrying the IO operation
|
/// - `Interrupted` is handled internally, by retrying the IO operation
|
||||||
/// - `WouldBlock` is handled by returning `Ok(None)`,
|
/// - `WouldBlock` is handled by returning `Ok(None)`,
|
||||||
/// - Other errors are returned as is
|
/// - Other errors are returned as is
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// See [tutorial in the module](self).
|
||||||
pub fn nonblocking_handle_io_errors<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
|
pub fn nonblocking_handle_io_errors<R, E, F>(mut iofn: F) -> Result<Option<R>, E>
|
||||||
where
|
where
|
||||||
E: TryIoErrorKind,
|
E: TryIoErrorKind,
|
||||||
@@ -144,6 +478,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [std:io::Read] extension trait for call with [nonblocking_handle_io_errors] applied
|
||||||
pub trait ReadNonblockingWithBoringErrorsHandledExt {
|
pub trait ReadNonblockingWithBoringErrorsHandledExt {
|
||||||
/// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read]
|
/// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read]
|
||||||
fn read_nonblocking_with_boring_errors_handled(
|
fn read_nonblocking_with_boring_errors_handled(
|
||||||
@@ -161,7 +496,27 @@ impl<T: io::Read> ReadNonblockingWithBoringErrorsHandledExt for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for [std::io::Read] providing the ability to read
|
||||||
|
/// a buffer exactly
|
||||||
pub trait ReadExt {
|
pub trait ReadExt {
|
||||||
|
/// Version of [std::io::Read::read_exact] that throws if there
|
||||||
|
/// is extra data in the stream to be read
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::io::ReadExt;
|
||||||
|
///
|
||||||
|
/// let mut buf = [0u8; 4];
|
||||||
|
///
|
||||||
|
/// // Over or underlong buffer yields error
|
||||||
|
/// assert!(b"12345".as_slice().read_exact_til_end(&mut buf).is_err());
|
||||||
|
/// assert!(b"123".as_slice().read_exact_til_end(&mut buf).is_err());
|
||||||
|
///
|
||||||
|
/// // Buffer of precisely the right length leads to successful read
|
||||||
|
/// assert!(b"1234".as_slice().read_exact_til_end(&mut buf).is_ok());
|
||||||
|
/// assert_eq!(b"1234", &buf);
|
||||||
|
/// ```
|
||||||
fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()>;
|
fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,28 +8,37 @@ use crate::{
|
|||||||
result::ensure_or,
|
result::ensure_or,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Size in bytes of a message header carrying length information
|
||||||
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
|
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
/// Error enum to represent various boundary sanity check failures during buffer operations
|
||||||
pub enum SanityError {
|
pub enum SanityError {
|
||||||
#[error("Offset is out of 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,
|
OutOfBufferBounds,
|
||||||
#[error("Offset is out of 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,
|
OutOfMessageBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")]
|
#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")]
|
||||||
|
/// Error indicating that message exceeds available buffer space
|
||||||
pub struct MessageTooLargeError {
|
pub struct MessageTooLargeError {
|
||||||
msg_size: usize,
|
msg_size: usize,
|
||||||
buf_size: usize,
|
buf_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageTooLargeError {
|
impl MessageTooLargeError {
|
||||||
|
/// Creates a new MessageTooLargeError with the given message and buffer sizes
|
||||||
pub fn new(msg_size: usize, buf_size: usize) -> Self {
|
pub fn new(msg_size: usize, buf_size: usize) -> Self {
|
||||||
Self { msg_size, buf_size }
|
Self { msg_size, buf_size }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures that the message size fits within the buffer size
|
||||||
|
///
|
||||||
|
/// Returns Ok(()) if the message fits, otherwise returns an error with size details
|
||||||
pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> {
|
pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> {
|
||||||
let err = MessageTooLargeError { msg_size, buf_size };
|
let err = MessageTooLargeError { msg_size, buf_size };
|
||||||
ensure_or(msg_size <= buf_size, err)
|
ensure_or(msg_size <= buf_size, err)
|
||||||
@@ -37,12 +46,16 @@ impl MessageTooLargeError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// Return type for ReadFromIo operations that contains the number of bytes read and an optional message slice
|
||||||
pub struct ReadFromIoReturn<'a> {
|
pub struct ReadFromIoReturn<'a> {
|
||||||
|
/// Number of bytes read from the input
|
||||||
pub bytes_read: usize,
|
pub bytes_read: usize,
|
||||||
|
/// Optional slice containing the complete message, if one was read
|
||||||
pub message: Option<&'a mut [u8]>,
|
pub message: Option<&'a mut [u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ReadFromIoReturn<'a> {
|
impl<'a> ReadFromIoReturn<'a> {
|
||||||
|
/// 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 {
|
pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bytes_read,
|
bytes_read,
|
||||||
@@ -52,9 +65,12 @@ impl<'a> ReadFromIoReturn<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
/// An enum representing errors that can occur during read operations from I/O
|
||||||
pub enum ReadFromIoError {
|
pub enum ReadFromIoError {
|
||||||
|
/// Error occurred while reading from the underlying I/O stream
|
||||||
#[error("Error reading from the underlying stream")]
|
#[error("Error reading from the underlying stream")]
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
|
/// Error occurred because message size exceeded buffer capacity
|
||||||
#[error("Message size out of buffer bounds")]
|
#[error("Message size out of buffer bounds")]
|
||||||
MessageTooLargeError(#[from] MessageTooLargeError),
|
MessageTooLargeError(#[from] MessageTooLargeError),
|
||||||
}
|
}
|
||||||
@@ -69,6 +85,10 @@ impl TryIoErrorKind for ReadFromIoError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[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]>> {
|
pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
|
||||||
header: [u8; HEADER_SIZE],
|
header: [u8; HEADER_SIZE],
|
||||||
buf: Buf,
|
buf: Buf,
|
||||||
@@ -76,25 +96,33 @@ pub struct LengthPrefixDecoder<Buf: BorrowMut<[u8]>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
||||||
|
/// Creates a new LengthPrefixDecoder with the given buffer
|
||||||
pub fn new(buf: Buf) -> Self {
|
pub fn new(buf: Buf) -> Self {
|
||||||
let header = Default::default();
|
let header = Default::default();
|
||||||
let off = 0;
|
let off = 0;
|
||||||
Self { header, buf, off }
|
Self { header, buf, off }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clears and zeroes all internal state
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.zeroize()
|
self.zeroize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new LengthPrefixDecoder from its component parts
|
||||||
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
|
pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self {
|
||||||
Self { header, buf, off }
|
Self { header, buf, off }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the decoder and returns its component parts
|
||||||
pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) {
|
pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) {
|
||||||
let Self { header, buf, off } = self;
|
let Self { header, buf, off } = self;
|
||||||
(header, buf, off)
|
(header, buf, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a complete message from the given reader into the decoder.
|
||||||
|
///
|
||||||
|
/// 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>(
|
pub fn read_all_from_stdio<R: io::Read>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut r: R,
|
mut r: R,
|
||||||
@@ -125,6 +153,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads from the given reader into the decoder's internal buffers
|
||||||
pub fn read_from_stdio<R: io::Read>(
|
pub fn read_from_stdio<R: io::Read>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut r: R,
|
mut r: R,
|
||||||
@@ -150,6 +179,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the next buffer slice that can be written to
|
||||||
pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
pub fn next_slice_to_write_to(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||||
fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> {
|
fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> {
|
||||||
match buf.is_empty() {
|
match buf.is_empty() {
|
||||||
@@ -172,6 +202,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advances the internal offset by the specified number of bytes
|
||||||
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
|
pub fn advance(&mut self, count: usize) -> Result<(), SanityError> {
|
||||||
let off = self.off + count;
|
let off = self.off + count;
|
||||||
let msg_off = off.saturating_sub(HEADER_SIZE);
|
let msg_off = off.saturating_sub(HEADER_SIZE);
|
||||||
@@ -189,6 +220,7 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> {
|
||||||
let buf_size = self.message_buffer().len();
|
let buf_size = self.message_buffer().len();
|
||||||
let msg_size = match self.get_header() {
|
let msg_size = match self.get_header() {
|
||||||
@@ -198,43 +230,53 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
MessageTooLargeError::ensure(msg_size, buf_size)
|
MessageTooLargeError::ensure(msg_size, buf_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the header buffer
|
||||||
pub fn header_buffer(&self) -> &[u8] {
|
pub fn header_buffer(&self) -> &[u8] {
|
||||||
&self.header[..]
|
&self.header[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the header buffer
|
||||||
pub fn header_buffer_mut(&mut self) -> &mut [u8] {
|
pub fn header_buffer_mut(&mut self) -> &mut [u8] {
|
||||||
&mut self.header[..]
|
&mut self.header[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the message buffer
|
||||||
pub fn message_buffer(&self) -> &[u8] {
|
pub fn message_buffer(&self) -> &[u8] {
|
||||||
self.buf.borrow()
|
self.buf.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the message buffer
|
||||||
pub fn message_buffer_mut(&mut self) -> &mut [u8] {
|
pub fn message_buffer_mut(&mut self) -> &mut [u8] {
|
||||||
self.buf.borrow_mut()
|
self.buf.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bytes read so far
|
||||||
pub fn bytes_read(&self) -> &usize {
|
pub fn bytes_read(&self) -> &usize {
|
||||||
&self.off
|
&self.off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the decoder and returns just the message buffer
|
||||||
pub fn into_message_buffer(self) -> Buf {
|
pub fn into_message_buffer(self) -> Buf {
|
||||||
let Self { buf, .. } = self;
|
let Self { buf, .. } = self;
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current offset into the header buffer
|
||||||
pub fn header_buffer_offset(&self) -> usize {
|
pub fn header_buffer_offset(&self) -> usize {
|
||||||
min(self.off, HEADER_SIZE)
|
min(self.off, HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current offset into the message buffer
|
||||||
pub fn message_buffer_offset(&self) -> usize {
|
pub fn message_buffer_offset(&self) -> usize {
|
||||||
self.off.saturating_sub(HEADER_SIZE)
|
self.off.saturating_sub(HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether a complete header has been read
|
||||||
pub fn has_header(&self) -> bool {
|
pub fn has_header(&self) -> bool {
|
||||||
self.header_buffer_offset() == HEADER_SIZE
|
self.header_buffer_offset() == HEADER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether a complete message has been read
|
||||||
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
|
pub fn has_message(&self) -> Result<bool, MessageTooLargeError> {
|
||||||
self.ensure_sufficient_msg_buffer()?;
|
self.ensure_sufficient_msg_buffer()?;
|
||||||
let msg_size = match self.get_header() {
|
let msg_size = match self.get_header() {
|
||||||
@@ -244,46 +286,55 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
Ok(self.message_buffer_avail().len() == msg_size)
|
Ok(self.message_buffer_avail().len() == msg_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the available data in the header buffer
|
||||||
pub fn header_buffer_avail(&self) -> &[u8] {
|
pub fn header_buffer_avail(&self) -> &[u8] {
|
||||||
let off = self.header_buffer_offset();
|
let off = self.header_buffer_offset();
|
||||||
&self.header_buffer()[..off]
|
&self.header_buffer()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the available data in the header buffer
|
||||||
pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
|
pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.header_buffer_offset();
|
let off = self.header_buffer_offset();
|
||||||
&mut self.header_buffer_mut()[..off]
|
&mut self.header_buffer_mut()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the remaining space in the header buffer
|
||||||
pub fn header_buffer_left(&self) -> &[u8] {
|
pub fn header_buffer_left(&self) -> &[u8] {
|
||||||
let off = self.header_buffer_offset();
|
let off = self.header_buffer_offset();
|
||||||
&self.header_buffer()[off..]
|
&self.header_buffer()[off..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the remaining space in the header buffer
|
||||||
pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
|
pub fn header_buffer_left_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.header_buffer_offset();
|
let off = self.header_buffer_offset();
|
||||||
&mut self.header_buffer_mut()[off..]
|
&mut self.header_buffer_mut()[off..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the available data in the message buffer
|
||||||
pub fn message_buffer_avail(&self) -> &[u8] {
|
pub fn message_buffer_avail(&self) -> &[u8] {
|
||||||
let off = self.message_buffer_offset();
|
let off = self.message_buffer_offset();
|
||||||
&self.message_buffer()[..off]
|
&self.message_buffer()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the available data in the message buffer
|
||||||
pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
|
pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.message_buffer_offset();
|
let off = self.message_buffer_offset();
|
||||||
&mut self.message_buffer_mut()[..off]
|
&mut self.message_buffer_mut()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the remaining space in the message buffer
|
||||||
pub fn message_buffer_left(&self) -> &[u8] {
|
pub fn message_buffer_left(&self) -> &[u8] {
|
||||||
let off = self.message_buffer_offset();
|
let off = self.message_buffer_offset();
|
||||||
&self.message_buffer()[off..]
|
&self.message_buffer()[off..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the remaining space in the message buffer
|
||||||
pub fn message_buffer_left_mut(&mut self) -> &mut [u8] {
|
pub fn message_buffer_left_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.message_buffer_offset();
|
let off = self.message_buffer_offset();
|
||||||
&mut self.message_buffer_mut()[off..]
|
&mut self.message_buffer_mut()[off..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the message size from the header if available
|
||||||
pub fn get_header(&self) -> Option<usize> {
|
pub fn get_header(&self) -> Option<usize> {
|
||||||
match self.header_buffer_offset() == HEADER_SIZE {
|
match self.header_buffer_offset() == HEADER_SIZE {
|
||||||
false => None,
|
false => None,
|
||||||
@@ -291,19 +342,23 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the message if header is available
|
||||||
pub fn message_size(&self) -> Option<usize> {
|
pub fn message_size(&self) -> Option<usize> {
|
||||||
self.get_header()
|
self.get_header()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the total size of the encoded message including header
|
||||||
pub fn encoded_message_bytes(&self) -> Option<usize> {
|
pub fn encoded_message_bytes(&self) -> Option<usize> {
|
||||||
self.message_size().map(|sz| sz + HEADER_SIZE)
|
self.message_size().map(|sz| sz + HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the message fragment if available
|
||||||
pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
pub fn message_fragment(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||||
self.ensure_sufficient_msg_buffer()?;
|
self.ensure_sufficient_msg_buffer()?;
|
||||||
Ok(self.message_size().map(|sz| &self.message_buffer()[..sz]))
|
Ok(self.message_size().map(|sz| &self.message_buffer()[..sz]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the message fragment if available
|
||||||
pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
pub fn message_fragment_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||||
self.ensure_sufficient_msg_buffer()?;
|
self.ensure_sufficient_msg_buffer()?;
|
||||||
Ok(self
|
Ok(self
|
||||||
@@ -311,12 +366,14 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
.map(|sz| &mut self.message_buffer_mut()[..sz]))
|
.map(|sz| &mut self.message_buffer_mut()[..sz]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the available data in the message fragment
|
||||||
pub fn message_fragment_avail(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
pub fn message_fragment_avail(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||||
let off = self.message_buffer_avail().len();
|
let off = self.message_buffer_avail().len();
|
||||||
self.message_fragment()
|
self.message_fragment()
|
||||||
.map(|frag| frag.map(|frag| &frag[..off]))
|
.map(|frag| frag.map(|frag| &frag[..off]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the available data in the message fragment
|
||||||
pub fn message_fragment_avail_mut(
|
pub fn message_fragment_avail_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||||
@@ -325,24 +382,28 @@ impl<Buf: BorrowMut<[u8]>> LengthPrefixDecoder<Buf> {
|
|||||||
.map(|frag| frag.map(|frag| &mut frag[..off]))
|
.map(|frag| frag.map(|frag| &mut frag[..off]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the remaining space in the message fragment
|
||||||
pub fn message_fragment_left(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
pub fn message_fragment_left(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||||
let off = self.message_buffer_avail().len();
|
let off = self.message_buffer_avail().len();
|
||||||
self.message_fragment()
|
self.message_fragment()
|
||||||
.map(|frag| frag.map(|frag| &frag[off..]))
|
.map(|frag| frag.map(|frag| &frag[off..]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
pub fn message_fragment_left_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||||
let off = self.message_buffer_avail().len();
|
let off = self.message_buffer_avail().len();
|
||||||
self.message_fragment_mut()
|
self.message_fragment_mut()
|
||||||
.map(|frag| frag.map(|frag| &mut frag[off..]))
|
.map(|frag| frag.map(|frag| &mut frag[off..]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of the complete message if available
|
||||||
pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
pub fn message(&self) -> Result<Option<&[u8]>, MessageTooLargeError> {
|
||||||
let sz = self.message_size();
|
let sz = self.message_size();
|
||||||
self.message_fragment_avail()
|
self.message_fragment_avail()
|
||||||
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag)))
|
.map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable slice of the complete message if available
|
||||||
pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
pub fn message_mut(&mut self) -> Result<Option<&mut [u8]>, MessageTooLargeError> {
|
||||||
let sz = self.message_size();
|
let sz = self.message_size();
|
||||||
self.message_fragment_avail_mut()
|
self.message_fragment_avail_mut()
|
||||||
|
|||||||
@@ -9,46 +9,61 @@ use zeroize::Zeroize;
|
|||||||
|
|
||||||
use crate::{io::IoResultKindHintExt, result::ensure_or};
|
use crate::{io::IoResultKindHintExt, result::ensure_or};
|
||||||
|
|
||||||
|
/// Size of the length prefix header in bytes - equal to the size of a u64
|
||||||
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
|
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
#[error("Write position is out of buffer bounds")]
|
#[error("Write position is out of buffer bounds")]
|
||||||
|
/// Error type indicating that a write position is beyond the boundaries of the allocated buffer
|
||||||
pub struct PositionOutOfBufferBounds;
|
pub struct PositionOutOfBufferBounds;
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
#[error("Write position is out of message bounds")]
|
#[error("Write position is out of message bounds")]
|
||||||
|
/// Error type indicating that a write position is beyond the boundaries of the message
|
||||||
pub struct PositionOutOfMessageBounds;
|
pub struct PositionOutOfMessageBounds;
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
#[error("Write position is out of header bounds")]
|
#[error("Write position is out of header bounds")]
|
||||||
|
/// Error type indicating that a write position is beyond the boundaries of the header
|
||||||
pub struct PositionOutOfHeaderBounds;
|
pub struct PositionOutOfHeaderBounds;
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
#[error("Message length is bigger than buffer length")]
|
#[error("Message length is bigger than buffer length")]
|
||||||
|
/// Error type indicating that the message length is larger than the available buffer space
|
||||||
pub struct MessageTooLarge;
|
pub struct MessageTooLarge;
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
|
/// Error type for message length sanity checks
|
||||||
pub enum MessageLenSanityError {
|
pub enum MessageLenSanityError {
|
||||||
|
/// Error indicating position is beyond message boundaries
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
||||||
|
/// Error indicating message length exceeds buffer capacity
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
MessageTooLarge(#[from] MessageTooLarge),
|
MessageTooLarge(#[from] MessageTooLarge),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
|
/// Error type for position bounds checking
|
||||||
pub enum PositionSanityError {
|
pub enum PositionSanityError {
|
||||||
|
/// Error indicating position is beyond message boundaries
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
||||||
|
/// Error indicating position is beyond buffer boundaries
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
|
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
|
/// Error type combining all sanity check errors
|
||||||
pub enum SanityError {
|
pub enum SanityError {
|
||||||
|
/// Error indicating position is beyond message boundaries
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds),
|
||||||
|
/// Error indicating position is beyond buffer boundaries
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
|
PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds),
|
||||||
|
/// Error indicating message length exceeds buffer capacity
|
||||||
#[error("{0:?}")]
|
#[error("{0:?}")]
|
||||||
MessageTooLarge(#[from] MessageTooLarge),
|
MessageTooLarge(#[from] MessageTooLarge),
|
||||||
}
|
}
|
||||||
@@ -86,12 +101,16 @@ impl From<PositionSanityError> for SanityError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Result of a write operation on an IO stream
|
||||||
pub struct WriteToIoReturn {
|
pub struct WriteToIoReturn {
|
||||||
|
/// Number of bytes successfully written in this operation
|
||||||
pub bytes_written: usize,
|
pub bytes_written: usize,
|
||||||
|
/// Whether the write operation has completed fully
|
||||||
pub done: bool,
|
pub done: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
/// Length-prefixed encoder that adds a length header to data before writing
|
||||||
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
|
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
|
||||||
buf: Buf,
|
buf: Buf,
|
||||||
header: [u8; HEADER_SIZE],
|
header: [u8; HEADER_SIZE],
|
||||||
@@ -99,6 +118,7 @@ pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
||||||
|
/// Creates a new encoder from a buffer
|
||||||
pub fn from_buffer(buf: Buf) -> Self {
|
pub fn from_buffer(buf: Buf) -> Self {
|
||||||
let (header, pos) = ([0u8; HEADER_SIZE], 0);
|
let (header, pos) = ([0u8; HEADER_SIZE], 0);
|
||||||
let mut r = Self { buf, header, pos };
|
let mut r = Self { buf, header, pos };
|
||||||
@@ -106,6 +126,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new encoder using the full buffer as a message
|
||||||
pub fn from_message(msg: Buf) -> Self {
|
pub fn from_message(msg: Buf) -> Self {
|
||||||
let mut r = Self::from_buffer(msg);
|
let mut r = Self::from_buffer(msg);
|
||||||
r.restart_write_with_new_message(r.buffer_bytes().len())
|
r.restart_write_with_new_message(r.buffer_bytes().len())
|
||||||
@@ -113,23 +134,27 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new encoder using part of the buffer as a message
|
||||||
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
|
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
|
||||||
let mut r = Self::from_message(msg);
|
let mut r = Self::from_message(msg);
|
||||||
r.set_message_len(len)?;
|
r.set_message_len(len)?;
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new encoder from buffer, message length and write position
|
||||||
pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result<Self, SanityError> {
|
pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result<Self, SanityError> {
|
||||||
let mut r = Self::from_buffer(buf);
|
let mut r = Self::from_buffer(buf);
|
||||||
r.set_msg_len_and_position(len, pos)?;
|
r.set_msg_len_and_position(len, pos)?;
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the encoder and returns the underlying buffer
|
||||||
pub fn into_buffer(self) -> Buf {
|
pub fn into_buffer(self) -> Buf {
|
||||||
let Self { buf, .. } = self;
|
let Self { buf, .. } = self;
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the encoder and returns buffer, message length and write position
|
||||||
pub fn into_parts(self) -> (Buf, usize, usize) {
|
pub fn into_parts(self) -> (Buf, usize, usize) {
|
||||||
let len = self.message_len();
|
let len = self.message_len();
|
||||||
let pos = self.writing_position();
|
let pos = self.writing_position();
|
||||||
@@ -137,11 +162,13 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
(buf, len, pos)
|
(buf, len, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the encoder state
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.set_msg_len_and_position(0, 0).unwrap();
|
self.set_msg_len_and_position(0, 0).unwrap();
|
||||||
self.set_message_offset(0).unwrap();
|
self.set_message_offset(0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the full message to an IO writer, retrying on interrupts
|
||||||
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
|
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
|
||||||
use io::ErrorKind as K;
|
use io::ErrorKind as K;
|
||||||
loop {
|
loop {
|
||||||
@@ -158,6 +185,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status
|
||||||
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
|
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
|
||||||
if self.exhausted() {
|
if self.exhausted() {
|
||||||
return Ok(WriteToIoReturn {
|
return Ok(WriteToIoReturn {
|
||||||
@@ -177,10 +205,12 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets write position to start for restarting output
|
||||||
pub fn restart_write(&mut self) {
|
pub fn restart_write(&mut self) {
|
||||||
self.set_writing_position(0).unwrap()
|
self.set_writing_position(0).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets write position to start and updates message length for restarting with new data
|
||||||
pub fn restart_write_with_new_message(
|
pub fn restart_write_with_new_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
len: usize,
|
len: usize,
|
||||||
@@ -189,6 +219,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
.map_err(|e| e.try_into().unwrap())
|
.map_err(|e| e.try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the next unwritten slice of data to write from header or message
|
||||||
pub fn next_slice_to_write(&self) -> &[u8] {
|
pub fn next_slice_to_write(&self) -> &[u8] {
|
||||||
let s = self.header_left();
|
let s = self.header_left();
|
||||||
if !s.is_empty() {
|
if !s.is_empty() {
|
||||||
@@ -203,66 +234,82 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if all data including header and message has been written
|
||||||
pub fn exhausted(&self) -> bool {
|
pub fn exhausted(&self) -> bool {
|
||||||
self.next_slice_to_write().is_empty()
|
self.next_slice_to_write().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing full message data
|
||||||
pub fn message(&self) -> &[u8] {
|
pub fn message(&self) -> &[u8] {
|
||||||
&self.buffer_bytes()[..self.message_len()]
|
&self.buffer_bytes()[..self.message_len()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing written portion of length header
|
||||||
pub fn header_written(&self) -> &[u8] {
|
pub fn header_written(&self) -> &[u8] {
|
||||||
&self.header()[..self.header_offset()]
|
&self.header()[..self.header_offset()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing unwritten portion of length header
|
||||||
pub fn header_left(&self) -> &[u8] {
|
pub fn header_left(&self) -> &[u8] {
|
||||||
&self.header()[self.header_offset()..]
|
&self.header()[self.header_offset()..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing written portion of message data
|
||||||
pub fn message_written(&self) -> &[u8] {
|
pub fn message_written(&self) -> &[u8] {
|
||||||
&self.message()[..self.message_offset()]
|
&self.message()[..self.message_offset()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing unwritten portion of message data
|
||||||
pub fn message_left(&self) -> &[u8] {
|
pub fn message_left(&self) -> &[u8] {
|
||||||
&self.message()[self.message_offset()..]
|
&self.message()[self.message_offset()..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns reference to underlying buffer
|
||||||
pub fn buf(&self) -> &Buf {
|
pub fn buf(&self) -> &Buf {
|
||||||
&self.buf
|
&self.buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice view of underlying buffer bytes
|
||||||
pub fn buffer_bytes(&self) -> &[u8] {
|
pub fn buffer_bytes(&self) -> &[u8] {
|
||||||
self.buf().borrow()
|
self.buf().borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes and returns length header value as u64
|
||||||
pub fn decode_header(&self) -> u64 {
|
pub fn decode_header(&self) -> u64 {
|
||||||
u64::from_le_bytes(self.header)
|
u64::from_le_bytes(self.header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns slice containing raw length header bytes
|
||||||
pub fn header(&self) -> &[u8; HEADER_SIZE] {
|
pub fn header(&self) -> &[u8; HEADER_SIZE] {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns decoded message length from header
|
||||||
pub fn message_len(&self) -> usize {
|
pub fn message_len(&self) -> usize {
|
||||||
self.decode_header() as usize
|
self.decode_header() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns total encoded size including header and message bytes
|
||||||
pub fn encoded_message_bytes(&self) -> usize {
|
pub fn encoded_message_bytes(&self) -> usize {
|
||||||
self.message_len() + HEADER_SIZE
|
self.message_len() + HEADER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns current write position within header and message
|
||||||
pub fn writing_position(&self) -> usize {
|
pub fn writing_position(&self) -> usize {
|
||||||
self.pos
|
self.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns write offset within length header bytes
|
||||||
pub fn header_offset(&self) -> usize {
|
pub fn header_offset(&self) -> usize {
|
||||||
min(self.writing_position(), HEADER_SIZE)
|
min(self.writing_position(), HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns write offset within message bytes
|
||||||
pub fn message_offset(&self) -> usize {
|
pub fn message_offset(&self) -> usize {
|
||||||
self.writing_position().saturating_sub(HEADER_SIZE)
|
self.writing_position().saturating_sub(HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets new length header bytes with bounds checking
|
||||||
pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> {
|
pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> {
|
||||||
self.offset_transaction(|t| {
|
self.offset_transaction(|t| {
|
||||||
t.header = header;
|
t.header = header;
|
||||||
@@ -272,14 +319,17 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encodes and sets length header value with bounds checking
|
||||||
pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> {
|
pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> {
|
||||||
self.set_header(header.to_le_bytes())
|
self.set_header(header.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets message lengthwith bounds checking
|
||||||
pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> {
|
pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> {
|
||||||
self.encode_and_set_header(len as u64)
|
self.encode_and_set_header(len as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets write position with message and buffer bounds checking
|
||||||
pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> {
|
pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> {
|
||||||
self.offset_transaction(|t| {
|
self.offset_transaction(|t| {
|
||||||
t.pos = pos;
|
t.pos = pos;
|
||||||
@@ -289,20 +339,24 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets write position within header bytes with bounds checking
|
||||||
pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> {
|
pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> {
|
||||||
ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?;
|
ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?;
|
||||||
self.set_writing_position(off).unwrap();
|
self.set_writing_position(off).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets write position within message bytes with bounds checking
|
||||||
pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> {
|
pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> {
|
||||||
self.set_writing_position(off + HEADER_SIZE)
|
self.set_writing_position(off + HEADER_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advances write position by specified offset with bounds checking
|
||||||
pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> {
|
pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> {
|
||||||
self.set_writing_position(self.writing_position() + off)
|
self.set_writing_position(self.writing_position() + off)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets message length and write position with bounds checking
|
||||||
pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> {
|
pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> {
|
||||||
self.pos = 0;
|
self.pos = 0;
|
||||||
self.set_message_len(len)?;
|
self.set_message_len(len)?;
|
||||||
@@ -347,24 +401,29 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Buf: BorrowMut<[u8]>> LengthPrefixEncoder<Buf> {
|
impl<Buf: BorrowMut<[u8]>> LengthPrefixEncoder<Buf> {
|
||||||
|
/// Gets a mutable reference to the underlying buffer
|
||||||
pub fn buf_mut(&mut self) -> &mut Buf {
|
pub fn buf_mut(&mut self) -> &mut Buf {
|
||||||
&mut self.buf
|
&mut self.buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the buffer as mutable bytes
|
||||||
pub fn buffer_bytes_mut(&mut self) -> &mut [u8] {
|
pub fn buffer_bytes_mut(&mut self) -> &mut [u8] {
|
||||||
self.buf.borrow_mut()
|
self.buf.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the message slice
|
||||||
pub fn message_mut(&mut self) -> &mut [u8] {
|
pub fn message_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.message_len();
|
let off = self.message_len();
|
||||||
&mut self.buffer_bytes_mut()[..off]
|
&mut self.buffer_bytes_mut()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the written portion of the message
|
||||||
pub fn message_written_mut(&mut self) -> &mut [u8] {
|
pub fn message_written_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.message_offset();
|
let off = self.message_offset();
|
||||||
&mut self.message_mut()[..off]
|
&mut self.message_mut()[..off]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the unwritten portion of the message
|
||||||
pub fn message_left_mut(&mut self) -> &mut [u8] {
|
pub fn message_left_mut(&mut self) -> &mut [u8] {
|
||||||
let off = self.message_offset();
|
let off = self.message_offset();
|
||||||
&mut self.message_mut()[off..]
|
&mut self.message_mut()[off..]
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
/// Module that handles decoding functionality
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
|
/// Module that handles encoding functionality
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
|
|||||||
@@ -1,18 +1,38 @@
|
|||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(clippy::missing_docs_in_private_items)]
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
|
//! Core utility functions and types used across the codebase.
|
||||||
|
|
||||||
|
/// Base64 encoding and decoding functionality.
|
||||||
pub mod b64;
|
pub mod b64;
|
||||||
|
/// Build-time utilities and macros.
|
||||||
pub mod build;
|
pub mod build;
|
||||||
|
/// Control flow abstractions and utilities.
|
||||||
pub mod controlflow;
|
pub mod controlflow;
|
||||||
|
/// File descriptor utilities.
|
||||||
pub mod fd;
|
pub mod fd;
|
||||||
|
/// File system operations and handling.
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
/// Functional programming utilities.
|
||||||
pub mod functional;
|
pub mod functional;
|
||||||
|
/// Input/output operations.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Length prefix encoding schemes implementation.
|
||||||
pub mod length_prefix_encoding;
|
pub mod length_prefix_encoding;
|
||||||
|
/// Memory manipulation and allocation utilities.
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
|
/// MIO integration utilities.
|
||||||
pub mod mio;
|
pub mod mio;
|
||||||
|
/// Extended Option type functionality.
|
||||||
pub mod option;
|
pub mod option;
|
||||||
|
/// Extended Result type functionality.
|
||||||
pub mod result;
|
pub mod result;
|
||||||
|
/// Time and duration utilities.
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
/// Type-level numbers and arithmetic.
|
||||||
pub mod typenum;
|
pub mod typenum;
|
||||||
|
/// Zero-copy serialization utilities.
|
||||||
pub mod zerocopy;
|
pub mod zerocopy;
|
||||||
|
/// Memory wiping utilities.
|
||||||
pub mod zeroize;
|
pub mod zeroize;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ macro_rules! cat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consistent inout ordering
|
// TODO: consistent inout ordering
|
||||||
|
/// 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) {
|
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||||
dst.borrow_mut().copy_from_slice(src.borrow());
|
dst.borrow_mut().copy_from_slice(src.borrow());
|
||||||
}
|
}
|
||||||
@@ -41,11 +42,13 @@ pub struct Forgetting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Forgetting<T> {
|
impl<T> Forgetting<T> {
|
||||||
|
/// Creates a new `Forgetting<T>` instance containing the given value.
|
||||||
pub fn new(value: T) -> Self {
|
pub fn new(value: T) -> Self {
|
||||||
let value = Some(value);
|
let value = Some(value);
|
||||||
Self { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts and returns the contained value, consuming self.
|
||||||
pub fn extract(mut self) -> T {
|
pub fn extract(mut self) -> T {
|
||||||
let mut value = None;
|
let mut value = None;
|
||||||
swap(&mut value, &mut self.value);
|
swap(&mut value, &mut self.value);
|
||||||
@@ -93,7 +96,9 @@ impl<T> Drop for Forgetting<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait that provides a method to discard a value without explicitly handling its results.
|
||||||
pub trait DiscardResultExt {
|
pub trait DiscardResultExt {
|
||||||
|
/// Consumes and discards a value without doing anything with it.
|
||||||
fn discard_result(self);
|
fn discard_result(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +106,9 @@ impl<T> DiscardResultExt for T {
|
|||||||
fn discard_result(self) {}
|
fn discard_result(self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait that provides a method to explicitly forget values.
|
||||||
pub trait ForgetExt {
|
pub trait ForgetExt {
|
||||||
|
/// Consumes and forgets a value, preventing its destructor from running.
|
||||||
fn forget(self);
|
fn forget(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +118,11 @@ impl<T> ForgetExt for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait that provides methods for swapping values.
|
||||||
pub trait SwapWithExt {
|
pub trait SwapWithExt {
|
||||||
|
/// Takes ownership of `other` and swaps its value with `self`, returning the original value.
|
||||||
fn swap_with(&mut self, other: Self) -> Self;
|
fn swap_with(&mut self, other: Self) -> Self;
|
||||||
|
/// Swaps the values between `self` and `other` in place.
|
||||||
fn swap_with_mut(&mut self, other: &mut Self);
|
fn swap_with_mut(&mut self, other: &mut Self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +137,9 @@ impl<T> SwapWithExt for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait that provides methods for swapping values with default values.
|
||||||
pub trait SwapWithDefaultExt {
|
pub trait SwapWithDefaultExt {
|
||||||
|
/// Takes the current value and replaces it with the default value, returning the original.
|
||||||
fn swap_with_default(&mut self) -> Self;
|
fn swap_with_default(&mut self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +149,7 @@ impl<T: Default> SwapWithDefaultExt for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait that provides a method to explicitly move values.
|
||||||
pub trait MoveExt {
|
pub trait MoveExt {
|
||||||
/// Deliberately move the value
|
/// Deliberately move the value
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
use mio::net::{UnixListener, UnixStream};
|
use mio::net::{UnixListener, UnixStream};
|
||||||
use rustix::fd::{OwnedFd, RawFd};
|
use std::os::fd::{OwnedFd, RawFd};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fd::{claim_fd, claim_fd_inplace},
|
fd::{claim_fd, claim_fd_inplace},
|
||||||
result::OkExt,
|
result::OkExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Module containing I/O interest flags for Unix operations
|
||||||
pub mod interest {
|
pub mod interest {
|
||||||
use mio::Interest;
|
use mio::Interest;
|
||||||
|
|
||||||
|
/// Interest flag indicating readability
|
||||||
pub const R: Interest = Interest::READABLE;
|
pub const R: Interest = Interest::READABLE;
|
||||||
|
|
||||||
|
/// Interest flag indicating writability
|
||||||
pub const W: Interest = Interest::WRITABLE;
|
pub const W: Interest = Interest::WRITABLE;
|
||||||
|
|
||||||
|
/// Interest flag indicating both readability and writability
|
||||||
pub const RW: Interest = R.add(W);
|
pub const RW: Interest = R.add(W);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing additional functionality for Unix listener
|
||||||
pub trait UnixListenerExt: Sized {
|
pub trait UnixListenerExt: Sized {
|
||||||
|
/// Creates a new Unix listener by claiming ownership of a raw file descriptor
|
||||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,9 +36,15 @@ impl UnixListenerExt for UnixListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing additional functionality for Unix streams
|
||||||
pub trait UnixStreamExt: Sized {
|
pub trait UnixStreamExt: Sized {
|
||||||
|
/// Creates a new Unix stream from an owned file descriptor
|
||||||
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
|
fn from_fd(fd: OwnedFd) -> anyhow::Result<Self>;
|
||||||
|
|
||||||
|
/// Claims ownership of a raw file descriptor and creates a new Unix stream
|
||||||
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
fn claim_fd(fd: RawFd) -> anyhow::Result<Self>;
|
||||||
|
|
||||||
|
/// Claims ownership of a raw file descriptor in place and creates a new Unix stream
|
||||||
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
|
fn claim_fd_inplace(fd: RawFd) -> anyhow::Result<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ use std::{
|
|||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::Read,
|
io::Read,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
os::fd::OwnedFd,
|
os::fd::{FromRawFd, OwnedFd},
|
||||||
};
|
};
|
||||||
use uds::UnixStreamExt as FdPassingExt;
|
use uds::UnixStreamExt as FdPassingExt;
|
||||||
|
|
||||||
use crate::fd::{claim_fd_inplace, IntoStdioErr};
|
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.
|
||||||
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
|
pub struct ReadWithFileDescriptors<const MAX_FDS: usize, Sock, BorrowSock, BorrowFds>
|
||||||
where
|
where
|
||||||
Sock: FdPassingExt,
|
Sock: FdPassingExt,
|
||||||
@@ -27,6 +30,8 @@ where
|
|||||||
BorrowSock: Borrow<Sock>,
|
BorrowSock: Borrow<Sock>,
|
||||||
BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
|
BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
|
||||||
{
|
{
|
||||||
|
/// Creates a new `ReadWithFileDescriptors` by wrapping a socket and a file
|
||||||
|
/// descriptor queue.
|
||||||
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
|
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
|
||||||
let _sock_dummy = PhantomData;
|
let _sock_dummy = PhantomData;
|
||||||
Self {
|
Self {
|
||||||
@@ -36,19 +41,24 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the wrapper and returns the underlying socket and file
|
||||||
|
/// descriptor queue.
|
||||||
pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
|
pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
|
||||||
let Self { socket, fds, .. } = self;
|
let Self { socket, fds, .. } = self;
|
||||||
(socket, fds)
|
(socket, fds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying socket.
|
||||||
pub fn socket(&self) -> &Sock {
|
pub fn socket(&self) -> &Sock {
|
||||||
self.socket.borrow()
|
self.socket.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the file descriptor queue.
|
||||||
pub fn fds(&self) -> &VecDeque<OwnedFd> {
|
pub fn fds(&self) -> &VecDeque<OwnedFd> {
|
||||||
self.fds.borrow()
|
self.fds.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the file descriptor queue.
|
||||||
pub fn fds_mut(&mut self) -> &mut VecDeque<OwnedFd> {
|
pub fn fds_mut(&mut self) -> &mut VecDeque<OwnedFd> {
|
||||||
self.fds.borrow_mut()
|
self.fds.borrow_mut()
|
||||||
}
|
}
|
||||||
@@ -61,6 +71,7 @@ where
|
|||||||
BorrowSock: BorrowMut<Sock>,
|
BorrowSock: BorrowMut<Sock>,
|
||||||
BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
|
BorrowFds: BorrowMut<VecDeque<OwnedFd>>,
|
||||||
{
|
{
|
||||||
|
/// Returns a mutable reference to the underlying socket.
|
||||||
pub fn socket_mut(&mut self) -> &mut Sock {
|
pub fn socket_mut(&mut self) -> &mut Sock {
|
||||||
self.socket.borrow_mut()
|
self.socket.borrow_mut()
|
||||||
}
|
}
|
||||||
@@ -115,7 +126,7 @@ where
|
|||||||
|
|
||||||
// Close the remaining fds
|
// Close the remaining fds
|
||||||
for fd in fd_iter {
|
for fd in fd_iter {
|
||||||
unsafe { rustix::io::close(*fd) };
|
unsafe { drop(OwnedFd::from_raw_fd(*fd)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
claim_fd_result
|
claim_fd_result
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rustix::fd::{AsFd, AsRawFd};
|
use std::os::fd::{AsFd, AsRawFd};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
cmp::min,
|
cmp::min,
|
||||||
@@ -10,6 +10,7 @@ use uds::UnixStreamExt as FdPassingExt;
|
|||||||
|
|
||||||
use crate::{repeat, return_if};
|
use crate::{repeat, return_if};
|
||||||
|
|
||||||
|
/// A structure that facilitates writing data and file descriptors to a Unix domain socket
|
||||||
pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds>
|
pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds>
|
||||||
where
|
where
|
||||||
Sock: FdPassingExt,
|
Sock: FdPassingExt,
|
||||||
@@ -30,6 +31,7 @@ where
|
|||||||
BorrowSock: Borrow<Sock>,
|
BorrowSock: Borrow<Sock>,
|
||||||
BorrowFds: BorrowMut<VecDeque<Fd>>,
|
BorrowFds: BorrowMut<VecDeque<Fd>>,
|
||||||
{
|
{
|
||||||
|
/// Creates a new `WriteWithFileDescriptors` instance with the given socket and file descriptor queue
|
||||||
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
|
pub fn new(socket: BorrowSock, fds: BorrowFds) -> Self {
|
||||||
let _sock_dummy = PhantomData;
|
let _sock_dummy = PhantomData;
|
||||||
let _fd_dummy = PhantomData;
|
let _fd_dummy = PhantomData;
|
||||||
@@ -41,19 +43,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes this instance and returns the underlying socket and file descriptor queue
|
||||||
pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
|
pub fn into_parts(self) -> (BorrowSock, BorrowFds) {
|
||||||
let Self { socket, fds, .. } = self;
|
let Self { socket, fds, .. } = self;
|
||||||
(socket, fds)
|
(socket, fds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying socket
|
||||||
pub fn socket(&self) -> &Sock {
|
pub fn socket(&self) -> &Sock {
|
||||||
self.socket.borrow()
|
self.socket.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the file descriptor queue
|
||||||
pub fn fds(&self) -> &VecDeque<Fd> {
|
pub fn fds(&self) -> &VecDeque<Fd> {
|
||||||
self.fds.borrow()
|
self.fds.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the file descriptor queue
|
||||||
pub fn fds_mut(&mut self) -> &mut VecDeque<Fd> {
|
pub fn fds_mut(&mut self) -> &mut VecDeque<Fd> {
|
||||||
self.fds.borrow_mut()
|
self.fds.borrow_mut()
|
||||||
}
|
}
|
||||||
@@ -66,6 +72,7 @@ where
|
|||||||
BorrowSock: BorrowMut<Sock>,
|
BorrowSock: BorrowMut<Sock>,
|
||||||
BorrowFds: BorrowMut<VecDeque<Fd>>,
|
BorrowFds: BorrowMut<VecDeque<Fd>>,
|
||||||
{
|
{
|
||||||
|
/// Returns a mutable reference to the underlying socket
|
||||||
pub fn socket_mut(&mut self) -> &mut Sock {
|
pub fn socket_mut(&mut self) -> &mut Sock {
|
||||||
self.socket.borrow_mut()
|
self.socket.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
|
/// A helper trait for turning any type value into `Some(value)`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::option::SomeExt;
|
||||||
|
///
|
||||||
|
/// let x = 42;
|
||||||
|
/// let y = x.some();
|
||||||
|
///
|
||||||
|
/// assert_eq!(y, Some(42));
|
||||||
|
/// ```
|
||||||
pub trait SomeExt: Sized {
|
pub trait SomeExt: Sized {
|
||||||
|
/// Wraps the calling value in `Some()`.
|
||||||
fn some(self) -> Option<Self> {
|
fn some(self) -> Option<Self> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ macro_rules! attempt {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for the ok operation, which provides a way to convert a value into a Result
|
||||||
pub trait OkExt<E>: Sized {
|
pub trait OkExt<E>: Sized {
|
||||||
|
/// Wraps a value in a Result::Ok variant
|
||||||
fn ok(self) -> Result<Self, E>;
|
fn ok(self) -> Result<Self, E>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ impl<T, E> OkExt<E> for T {
|
|||||||
///
|
///
|
||||||
/// Implementations must not panic.
|
/// Implementations must not panic.
|
||||||
pub trait GuaranteedValue {
|
pub trait GuaranteedValue {
|
||||||
|
/// The value type that will be returned by guaranteed()
|
||||||
type Value;
|
type Value;
|
||||||
|
|
||||||
/// Extract the contained value while being panic-safe, like .unwrap()
|
/// Extract the contained value while being panic-safe, like .unwrap()
|
||||||
@@ -35,7 +38,11 @@ pub trait GuaranteedValue {
|
|||||||
fn guaranteed(self) -> Self::Value;
|
fn guaranteed(self) -> Self::Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for adding finally operation to types
|
||||||
pub trait FinallyExt {
|
pub trait FinallyExt {
|
||||||
|
/// Executes a closure with mutable access to self and returns self
|
||||||
|
///
|
||||||
|
/// The closure is guaranteed to be executed before returning.
|
||||||
fn finally<F: FnOnce(&mut Self)>(self, f: F) -> Self;
|
fn finally<F: FnOnce(&mut Self)>(self, f: F) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +132,18 @@ impl<T> GuaranteedValue for Guaranteed<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks a condition is true and returns an error if not.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_util::result::ensure_or;
|
||||||
|
/// let result = ensure_or(5 > 3, "not greater");
|
||||||
|
/// assert!(result.is_ok());
|
||||||
|
///
|
||||||
|
/// let result = ensure_or(5 < 3, "not less");
|
||||||
|
/// assert!(result.is_err());
|
||||||
|
/// ```
|
||||||
pub fn ensure_or<E>(b: bool, err: E) -> Result<(), E> {
|
pub fn ensure_or<E>(b: bool, err: E) -> Result<(), E> {
|
||||||
match b {
|
match b {
|
||||||
true => Ok(()),
|
true => Ok(()),
|
||||||
@@ -132,6 +151,18 @@ pub fn ensure_or<E>(b: bool, err: E) -> Result<(), E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluates to an error if the condition is true.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rosenpass_util::result::bail_if;
|
||||||
|
/// let result = bail_if(false, "not bailed");
|
||||||
|
/// assert!(result.is_ok());
|
||||||
|
///
|
||||||
|
/// let result = bail_if(true, "bailed");
|
||||||
|
/// assert!(result.is_err());
|
||||||
|
/// ```
|
||||||
pub fn bail_if<E>(b: bool, err: E) -> Result<(), E> {
|
pub fn bail_if<E>(b: bool, err: E) -> Result<(), E> {
|
||||||
ensure_or(!b, err)
|
ensure_or(!b, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ use std::time::Instant;
|
|||||||
/// This is a simple wrapper around `std::time::Instant` that provides a
|
/// This is a simple wrapper around `std::time::Instant` that provides a
|
||||||
/// convenient way to get the seconds elapsed since the creation of the
|
/// convenient way to get the seconds elapsed since the creation of the
|
||||||
/// `Timebase` instance.
|
/// `Timebase` instance.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosenpass_util::time::Timebase;
|
||||||
|
///
|
||||||
|
/// let timebase = Timebase::default();
|
||||||
|
/// let now = timebase.now();
|
||||||
|
/// assert!(now > 0.0);
|
||||||
|
/// ```
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Timebase(Instant);
|
pub struct Timebase(Instant);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ macro_rules! typenum2const {
|
|||||||
|
|
||||||
/// Trait implemented by constant integers to facilitate conversion to constant integers
|
/// Trait implemented by constant integers to facilitate conversion to constant integers
|
||||||
pub trait IntoConst<T> {
|
pub trait IntoConst<T> {
|
||||||
|
/// The constant value after conversion
|
||||||
const VALUE: T;
|
const VALUE: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,56 +7,68 @@ use zeroize::Zeroize;
|
|||||||
use crate::zeroize::ZeroizedExt;
|
use crate::zeroize::ZeroizedExt;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[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> {
|
pub struct RefMaker<B: Sized, T> {
|
||||||
buf: B,
|
buf: B,
|
||||||
_phantom_t: PhantomData<T>,
|
_phantom_t: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, T> RefMaker<B, T> {
|
impl<B, T> RefMaker<B, T> {
|
||||||
|
/// Creates a new RefMaker with the given buffer
|
||||||
pub fn new(buf: B) -> Self {
|
pub fn new(buf: B) -> Self {
|
||||||
let _phantom_t = PhantomData;
|
let _phantom_t = PhantomData;
|
||||||
Self { buf, _phantom_t }
|
Self { buf, _phantom_t }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size in bytes needed for target type T
|
||||||
pub const fn target_size() -> usize {
|
pub const fn target_size() -> usize {
|
||||||
std::mem::size_of::<T>()
|
std::mem::size_of::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes this RefMaker and returns the inner buffer
|
||||||
pub fn into_buf(self) -> B {
|
pub fn into_buf(self) -> B {
|
||||||
self.buf
|
self.buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner buffer
|
||||||
pub fn buf(&self) -> &B {
|
pub fn buf(&self) -> &B {
|
||||||
&self.buf
|
&self.buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the inner buffer
|
||||||
pub fn buf_mut(&mut self) -> &mut B {
|
pub fn buf_mut(&mut self) -> &mut B {
|
||||||
&mut self.buf
|
&mut self.buf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSlice, T> RefMaker<B, T> {
|
impl<B: ByteSlice, T> RefMaker<B, T> {
|
||||||
|
/// Parses the buffer into a reference of type T
|
||||||
pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
|
pub fn parse(self) -> anyhow::Result<Ref<B, T>> {
|
||||||
self.ensure_fit()?;
|
self.ensure_fit()?;
|
||||||
Ref::<B, T>::new(self.buf).context("Parser error!")
|
Ref::<B, T>::new(self.buf).context("Parser error!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits the buffer into a RefMaker containing the first `target_size` bytes and the remaining tail
|
||||||
pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> {
|
pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> {
|
||||||
self.ensure_fit()?;
|
self.ensure_fit()?;
|
||||||
let (head, tail) = self.buf.split_at(Self::target_size());
|
let (head, tail) = self.buf.split_at(Self::target_size());
|
||||||
Ok((Self::new(head), tail))
|
Ok((Self::new(head), tail))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits the buffer into two RefMakers, with the first containing the first `target_size` bytes
|
||||||
pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> {
|
pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> {
|
||||||
self.ensure_fit()?;
|
self.ensure_fit()?;
|
||||||
let (head, tail) = self.buf.split_at(Self::target_size());
|
let (head, tail) = self.buf.split_at(Self::target_size());
|
||||||
Ok((Self::new(head), Self::new(tail)))
|
Ok((Self::new(head), Self::new(tail)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a RefMaker containing only the first `target_size` bytes
|
||||||
pub fn from_prefix(self) -> anyhow::Result<Self> {
|
pub fn from_prefix(self) -> anyhow::Result<Self> {
|
||||||
Ok(Self::from_prefix_with_tail(self)?.0)
|
Ok(Self::from_prefix_with_tail(self)?.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)> {
|
pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> {
|
||||||
self.ensure_fit()?;
|
self.ensure_fit()?;
|
||||||
let point = self.bytes().len() - Self::target_size();
|
let point = self.bytes().len() - Self::target_size();
|
||||||
@@ -64,6 +76,7 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
|||||||
Ok((Self::new(tail), head))
|
Ok((Self::new(tail), head))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits the buffer into two RefMakers, with the second containing the last `target_size` bytes
|
||||||
pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
|
pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> {
|
||||||
self.ensure_fit()?;
|
self.ensure_fit()?;
|
||||||
let point = self.bytes().len() - Self::target_size();
|
let point = self.bytes().len() - Self::target_size();
|
||||||
@@ -71,14 +84,17 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
|||||||
Ok((Self::new(head), Self::new(tail)))
|
Ok((Self::new(head), Self::new(tail)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a RefMaker containing only the last `target_size` bytes
|
||||||
pub fn from_suffix(self) -> anyhow::Result<Self> {
|
pub fn from_suffix(self) -> anyhow::Result<Self> {
|
||||||
Ok(Self::from_suffix_with_head(self)?.0)
|
Ok(Self::from_suffix_with_head(self)?.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying bytes
|
||||||
pub fn bytes(&self) -> &[u8] {
|
pub fn bytes(&self) -> &[u8] {
|
||||||
self.buf().deref()
|
self.buf().deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures the buffer is large enough to hold type T
|
||||||
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
pub fn ensure_fit(&self) -> anyhow::Result<()> {
|
||||||
let have = self.bytes().len();
|
let have = self.bytes().len();
|
||||||
let need = Self::target_size();
|
let need = Self::target_size();
|
||||||
@@ -91,10 +107,12 @@ impl<B: ByteSlice, T> RefMaker<B, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<B: ByteSliceMut, T> RefMaker<B, T> {
|
impl<B: ByteSliceMut, T> RefMaker<B, T> {
|
||||||
|
/// Creates a zeroed reference of type T from the buffer
|
||||||
pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> {
|
pub fn make_zeroized(self) -> anyhow::Result<Ref<B, T>> {
|
||||||
self.zeroized().parse()
|
self.zeroized().parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying bytes
|
||||||
pub fn bytes_mut(&mut self) -> &mut [u8] {
|
pub fn bytes_mut(&mut self) -> &mut [u8] {
|
||||||
self.buf_mut().deref_mut()
|
self.buf_mut().deref_mut()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
||||||
|
|
||||||
|
/// A trait for converting a `Ref<B, T>` into a `Ref<&[u8], T>`.
|
||||||
pub trait ZerocopyEmancipateExt<B, T> {
|
pub trait ZerocopyEmancipateExt<B, T> {
|
||||||
|
/// Converts this reference into a reference backed by a byte slice.
|
||||||
fn emancipate(&self) -> Ref<&[u8], T>;
|
fn emancipate(&self) -> Ref<&[u8], T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait for converting a `Ref<B, T>` into a mutable `Ref<&mut [u8], T>`.
|
||||||
pub trait ZerocopyEmancipateMutExt<B, T> {
|
pub trait ZerocopyEmancipateMutExt<B, T> {
|
||||||
|
/// Converts this reference into a mutable reference backed by a byte slice.
|
||||||
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
|
fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,24 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
|
|||||||
|
|
||||||
use super::RefMaker;
|
use super::RefMaker;
|
||||||
|
|
||||||
|
/// Extension trait for zero-copy slice operations.
|
||||||
pub trait ZerocopySliceExt: Sized + ByteSlice {
|
pub trait ZerocopySliceExt: Sized + ByteSlice {
|
||||||
|
/// Creates a new `RefMaker` for the given slice.
|
||||||
fn zk_ref_maker<T>(self) -> RefMaker<Self, T> {
|
fn zk_ref_maker<T>(self) -> RefMaker<Self, T> {
|
||||||
RefMaker::<Self, T>::new(self)
|
RefMaker::<Self, T>::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the slice into a zero-copy reference.
|
||||||
fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_parse<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().parse()
|
self.zk_ref_maker().parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a prefix of the slice into a zero-copy reference.
|
||||||
fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_parse_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().from_prefix()?.parse()
|
self.zk_ref_maker().from_prefix()?.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a suffix of the slice into a zero-copy reference.
|
||||||
fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_parse_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().from_suffix()?.parse()
|
self.zk_ref_maker().from_suffix()?.parse()
|
||||||
}
|
}
|
||||||
@@ -22,15 +27,19 @@ pub trait ZerocopySliceExt: Sized + ByteSlice {
|
|||||||
|
|
||||||
impl<B: ByteSlice> ZerocopySliceExt for B {}
|
impl<B: ByteSlice> ZerocopySliceExt for B {}
|
||||||
|
|
||||||
|
/// Extension trait for zero-copy slice operations with mutable slices.
|
||||||
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
|
pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut {
|
||||||
|
/// Creates a new zeroed reference from the entire slice.
|
||||||
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_zeroized<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().make_zeroized()
|
self.zk_ref_maker().make_zeroized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new zeroed reference from a prefix of the slice.
|
||||||
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_zeroized_from_prefix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().from_prefix()?.make_zeroized()
|
self.zk_ref_maker().from_prefix()?.make_zeroized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new zeroed reference from a suffix of the slice.
|
||||||
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
fn zk_zeroized_from_suffix<T>(self) -> anyhow::Result<Ref<Self, T>> {
|
||||||
self.zk_ref_maker().from_suffix()?.make_zeroized()
|
self.zk_ref_maker().from_suffix()?.make_zeroized()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
/// Extension trait providing a method for zeroizing a value and returning it
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use zeroize::Zeroize;
|
||||||
|
/// use rosenpass_util::zeroize::ZeroizedExt;
|
||||||
|
///
|
||||||
|
/// let mut value = String::from("hello");
|
||||||
|
/// value.zeroize();
|
||||||
|
/// assert_eq!(value, "");
|
||||||
|
///
|
||||||
|
/// let value = String::from("hello").zeroized();
|
||||||
|
/// assert_eq!(value, "");
|
||||||
|
/// ```
|
||||||
pub trait ZeroizedExt: Zeroize + Sized {
|
pub trait ZeroizedExt: Zeroize + Sized {
|
||||||
|
/// Zeroizes the value in place and returns self
|
||||||
fn zeroized(mut self) -> Self {
|
fn zeroized(mut self) -> Self {
|
||||||
self.zeroize();
|
self.zeroize();
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -12,32 +12,32 @@ readme = "readme.md"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
zerocopy = { workspace = true }
|
zerocopy = { workspace = true }
|
||||||
rosenpass-secret-memory = {workspace = true}
|
rosenpass-secret-memory = { workspace = true }
|
||||||
|
|
||||||
# Privileged only
|
# Privileged only
|
||||||
wireguard-uapi = { workspace = true }
|
wireguard-uapi = { workspace = true }
|
||||||
|
|
||||||
# Socket handler only
|
# Socket handler only
|
||||||
rosenpass-to = { workspace = true }
|
rosenpass-to = { workspace = true }
|
||||||
tokio = { version = "1.39.3", features = ["sync", "full", "mio"] }
|
tokio = { version = "1.42.0", features = ["sync", "full", "mio"] }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
derive_builder = {workspace = true}
|
derive_builder = { workspace = true }
|
||||||
postcard = {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
|
# 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?
|
# Maybe something about the combination of features and optional crates?
|
||||||
rustix = { version = "0.38.27", optional = true }
|
rustix = { version = "0.38.41", optional = true }
|
||||||
libc = { version = "0.2", optional = true }
|
libc = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# Mio broker client
|
# Mio broker client
|
||||||
mio = { workspace = true }
|
mio = { workspace = true }
|
||||||
rosenpass-util = { workspace = true }
|
rosenpass-util = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = {workspace = true}
|
rand = { workspace = true }
|
||||||
procspawn = {workspace = true}
|
procspawn = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experiment_api = ["rustix", "libc"]
|
experiment_api = ["rustix", "libc"]
|
||||||
@@ -49,7 +49,7 @@ path = "src/bin/priviledged.rs"
|
|||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
required-features = ["experiment_api"]
|
required-features = ["experiment_api"]
|
||||||
cfg = { target_os = "linux" }
|
cfg = { target_os = "linux" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "rosenpass-wireguard-broker-socket-handler"
|
name = "rosenpass-wireguard-broker-socket-handler"
|
||||||
@@ -57,4 +57,4 @@ test = false
|
|||||||
path = "src/bin/socket_handler.rs"
|
path = "src/bin/socket_handler.rs"
|
||||||
doc = false
|
doc = false
|
||||||
required-features = ["experiment_api"]
|
required-features = ["experiment_api"]
|
||||||
cfg = { target_os = "linux" }
|
cfg = { target_os = "linux" }
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use rosenpass_to::{ops::copy_slice_least_src, To};
|
|||||||
use rosenpass_util::io::{IoResultKindHintExt, TryIoResultKindHintExt};
|
use rosenpass_util::io::{IoResultKindHintExt, TryIoResultKindHintExt};
|
||||||
use rosenpass_util::length_prefix_encoding::decoder::LengthPrefixDecoder;
|
use rosenpass_util::length_prefix_encoding::decoder::LengthPrefixDecoder;
|
||||||
use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
|
use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
|
||||||
use rustix::fd::AsFd;
|
|
||||||
use std::borrow::{Borrow, BorrowMut};
|
use std::borrow::{Borrow, BorrowMut};
|
||||||
|
use std::os::fd::AsFd;
|
||||||
|
|
||||||
use crate::api::client::{
|
use crate::api::client::{
|
||||||
BrokerClient, BrokerClientIo, BrokerClientPollResponseError, BrokerClientSetPskError,
|
BrokerClient, BrokerClientIo, BrokerClientPollResponseError, BrokerClientSetPskError,
|
||||||
|
|||||||
Reference in New Issue
Block a user