mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-01-13 08:19:47 -05:00
Merge branch 'dev' into 'main'
Merge Dev Multi-Crypto Support See merge request veilid/veilid!17
This commit is contained in:
commit
201bebc868
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@ -3,27 +3,44 @@
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"inputs": [
|
||||
{
|
||||
"id": "pickPid",
|
||||
"type": "promptString",
|
||||
"description": "Enter process id"
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"name": "Attach to veilid-server",
|
||||
"program": "${workspaceFolder}/target/debug/veilid-server",
|
||||
"pid": "${command:pickMyProcess}"
|
||||
"pid": "${command:pickMyProcess}",
|
||||
"sourceLanguages": [
|
||||
"rust"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"name": "Attach to veilid-cli",
|
||||
"program": "${workspaceFolder}/target/debug/veilid-cli",
|
||||
"pid": "${command:pickMyProcess}"
|
||||
"pid": "${command:pickMyProcess}",
|
||||
"sourceLanguages": [
|
||||
"rust"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"name": "Attach to veilid-flutter example",
|
||||
"program": "${workspaceFolder}/veilid-flutter/example/build/linux/x64/debug/bundle/veilid_example",
|
||||
"pid": "${command:pickMyProcess}"
|
||||
"pid": "${command:pickMyProcess}",
|
||||
"sourceLanguages": [
|
||||
"rust",
|
||||
"dart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
@ -42,16 +59,6 @@
|
||||
],
|
||||
"terminal": "console"
|
||||
},
|
||||
// {
|
||||
// "type": "lldb",
|
||||
// "request": "launch",
|
||||
// "name": "Debug veilid-server",
|
||||
// "cargo": {
|
||||
// "args": ["run", "--manifest-path", "veilid-server/Cargo.toml"]
|
||||
// },
|
||||
// "args": ["--trace"],
|
||||
// "cwd": "${workspaceFolder}/veilid-server"
|
||||
// }
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
@ -75,6 +82,7 @@
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--features=rt-tokio",
|
||||
"--manifest-path",
|
||||
"veilid-core/Cargo.toml"
|
||||
],
|
||||
|
397
Cargo.lock
generated
397
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,8 @@ deps-android:
|
||||
RUN mkdir /Android; mkdir /Android/Sdk
|
||||
RUN curl -o /Android/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip
|
||||
RUN cd /Android; unzip /Android/cmdline-tools.zip
|
||||
RUN yes | /Android/cmdline-tools/bin/sdkmanager --sdk_root=/Android/Sdk build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33
|
||||
RUN yes | /Android/cmdline-tools/bin/sdkmanager --sdk_root=/Android/Sdk build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 cmdline-tools\;latest
|
||||
RUN rm -rf /Android/cmdline-tools
|
||||
RUN apt-get clean
|
||||
|
||||
# Just linux build not android
|
||||
|
@ -35,6 +35,13 @@ command line without it. If you do so, you may skip to
|
||||
|
||||
#### Setup Dependencies using the CLI
|
||||
|
||||
|
||||
You can automatically install the prerequisites using this script:
|
||||
|
||||
```shell
|
||||
./install_linux_prerequisites.sh
|
||||
```
|
||||
|
||||
Otherwise, you may choose to use Android `sdkmanager`. Follow the installation
|
||||
instructions for `sdkmanager`
|
||||
[here](https://developer.android.com/studio/command-line/sdkmanager), then use
|
||||
|
@ -50,7 +50,6 @@ core:
|
||||
node_id: ''
|
||||
node_id_secret: ''
|
||||
bootstrap: ['bootstrap.dev.veilid.net']
|
||||
bootstrap_nodes: []
|
||||
routing_table:
|
||||
limit_over_attached: 64
|
||||
limit_fully_attached: 32
|
||||
|
@ -14,14 +14,14 @@ and the `veilid-server.conf` file.
|
||||
|
||||
## Global Directives
|
||||
|
||||
| Directive | Description |
|
||||
|-------------------------------|-----------------------------------------|
|
||||
| [daemon](#daemon) | Run `veilid-server` in the background |
|
||||
| [client\_api](#client_api) ||
|
||||
| [auto\_attach](#auto_attach) ||
|
||||
| [logging](#logging) ||
|
||||
| [testing](#testing) ||
|
||||
| [core](#core) ||
|
||||
| Directive | Description |
|
||||
| ---------------------------- | ------------------------------------- |
|
||||
| [daemon](#daemon) | Run `veilid-server` in the background |
|
||||
| [client\_api](#client_api) | |
|
||||
| [auto\_attach](#auto_attach) | |
|
||||
| [logging](#logging) | |
|
||||
| [testing](#testing) | |
|
||||
| [core](#core) | |
|
||||
|
||||
|
||||
### daemon
|
||||
@ -39,10 +39,10 @@ client_api:
|
||||
listen_address: 'localhost:5959'
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------------------------------------------|-------------|
|
||||
| [enabled](#client_apienabled) ||
|
||||
| [listen\_address](#client_apilisten_address) ||
|
||||
| Parameter | Description |
|
||||
| -------------------------------------------- | ----------- |
|
||||
| [enabled](#client_apienabled) | |
|
||||
| [listen\_address](#client_apilisten_address) | |
|
||||
|
||||
#### client\_api:enabled
|
||||
|
||||
@ -82,13 +82,13 @@ logging:
|
||||
grpc_endpoint: 'localhost:4317'
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------------------------|-------------|
|
||||
| [system](#loggingsystem) ||
|
||||
| [terminal](#loggingterminal) ||
|
||||
| [file](#loggingfile) ||
|
||||
| [api](#loggingapi) ||
|
||||
| [otlp](#loggingotlp) ||
|
||||
| Parameter | Description |
|
||||
| ---------------------------- | ----------- |
|
||||
| [system](#loggingsystem) | |
|
||||
| [terminal](#loggingterminal) | |
|
||||
| [file](#loggingfile) | |
|
||||
| [api](#loggingapi) | |
|
||||
| [otlp](#loggingotlp) | |
|
||||
|
||||
#### logging:system
|
||||
|
||||
@ -142,12 +142,12 @@ testing:
|
||||
|
||||
### core
|
||||
|
||||
| Parameter | Description |
|
||||
|-------------------------------------------|-------------|
|
||||
| [protected\_store](#coreprotected_store) ||
|
||||
| [table\_store](#coretable_store) ||
|
||||
| [block\_store](#block_store) ||
|
||||
| [network](#corenetwork) ||
|
||||
| Parameter | Description |
|
||||
| ---------------------------------------- | ----------- |
|
||||
| [protected\_store](#coreprotected_store) | |
|
||||
| [table\_store](#coretable_store) | |
|
||||
| [block\_store](#block_store) | |
|
||||
| [network](#corenetwork) | |
|
||||
|
||||
#### core:protected\_store
|
||||
|
||||
@ -191,7 +191,6 @@ network:
|
||||
node_id: ''
|
||||
node_id_secret: ''
|
||||
bootstrap: ['bootstrap.dev.veilid.net']
|
||||
bootstrap_nodes: []
|
||||
upnp: true
|
||||
detect_address_changes: true
|
||||
enable_local_peer_scope: false
|
||||
@ -199,13 +198,13 @@ network:
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---------------------------------------------|-------------|
|
||||
| [routing\_table](#corenetworkrouting_table) ||
|
||||
| [rpc](#corenetworkrpc) ||
|
||||
| [dht](#corenetworkdht) ||
|
||||
| [tls](#corenetworktls) ||
|
||||
| [application](#corenetworkapplication) ||
|
||||
| [protocol](#corenetworkprotocol) ||
|
||||
| ------------------------------------------- | ----------- |
|
||||
| [routing\_table](#corenetworkrouting_table) | |
|
||||
| [rpc](#corenetworkrpc) | |
|
||||
| [dht](#corenetworkdht) | |
|
||||
| [tls](#corenetworktls) | |
|
||||
| [application](#corenetworkapplication) | |
|
||||
| [protocol](#corenetworkprotocol) | |
|
||||
|
||||
#### core:network:routing\_table
|
||||
|
||||
|
2
external/hashlink
vendored
2
external/hashlink
vendored
@ -1 +1 @@
|
||||
Subproject commit a089b448071ef36633947693b90023c67dc8485f
|
||||
Subproject commit ddab4623e19a8b3e9e3ee48be999908eebc54301
|
33
install_linux_prerequisites.sh
Executable file
33
install_linux_prerequisites.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
if [ $(id -u) -eq 0 ]; then
|
||||
echo "Don't run this as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Install APT dependencies
|
||||
sudo apt update -y
|
||||
sudo apt install -y openjdk-11-jdk-headless iproute2 curl build-essential cmake libssl-dev openssl file git pkg-config libdbus-1-dev libdbus-glib-1-dev libgirepository1.0-dev libcairo2-dev checkinstall unzip llvm wabt checkinstall
|
||||
|
||||
# Install Rust
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -c clippy --profile default
|
||||
source "$HOME/.cargo/env"
|
||||
|
||||
# Install Android SDK
|
||||
mkdir $HOME/Android; mkdir $HOME/Android/Sdk
|
||||
curl -o $HOME/Android/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip
|
||||
cd $HOME/Android; unzip $HOME/Android/cmdline-tools.zip
|
||||
$HOME/Android/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/Android/Sdk build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 cmdline-tools\;latest emulator
|
||||
cd $HOME
|
||||
rm -rf $HOME/Android/cmdline-tools $HOME/Android/cmdline-tools.zip
|
||||
|
||||
# Add environment variables
|
||||
cat >> $HOME/.profile <<END
|
||||
source "\$HOME/.cargo/env"
|
||||
export PATH=\$PATH:\$HOME/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin:\$HOME/Android/Sdk/platform-tools:\$HOME/Android/Sdk/cmdline-tools/latest/bin
|
||||
export ANDROID_NDK_HOME=\$HOME/Android/Sdk/ndk/25.1.8937393
|
||||
export ANDROID_SDK_ROOT=\$HOME/Android/Sdk
|
||||
END
|
||||
|
||||
echo Exit and reopen the shell and continue with ./setup_linux.sh
|
@ -91,12 +91,6 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro
|
||||
# install cargo packages
|
||||
cargo install wasm-bindgen-cli wasm-pack
|
||||
|
||||
# Ensure packages are installed
|
||||
sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk llvm wabt checkinstall
|
||||
|
||||
# Ensure android sdk packages are installed
|
||||
$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33
|
||||
|
||||
# Install capnproto using the same mechanism as our earthly build
|
||||
$SCRIPTDIR/scripts/earthly/install_capnproto.sh
|
||||
# Install protoc using the same mechanism as our earthly build
|
||||
|
@ -96,6 +96,9 @@ impl veilid_client::Server for VeilidClientImpl {
|
||||
self.comproc.update_route(route);
|
||||
}
|
||||
VeilidUpdate::Shutdown => self.comproc.update_shutdown(),
|
||||
VeilidUpdate::ValueChange(value_change) => {
|
||||
self.comproc.update_value_change(value_change);
|
||||
}
|
||||
}
|
||||
|
||||
Promise::ok(())
|
||||
@ -192,45 +195,55 @@ impl ClientApiConnection {
|
||||
|
||||
let rpc_jh = spawn_local(rpc_system);
|
||||
|
||||
// Send the request and get the state object and the registration object
|
||||
let response = request
|
||||
.send()
|
||||
.promise
|
||||
.await
|
||||
.map_err(|e| format!("failed to send register request: {}", e))?;
|
||||
let response = response
|
||||
.get()
|
||||
.map_err(|e| format!("failed to get register response: {}", e))?;
|
||||
let reg_res: Result<registration::Client, String> = (async {
|
||||
// Send the request and get the state object and the registration object
|
||||
let response = request
|
||||
.send()
|
||||
.promise
|
||||
.await
|
||||
.map_err(|e| format!("failed to send register request: {}", e))?;
|
||||
let response = response
|
||||
.get()
|
||||
.map_err(|e| format!("failed to get register response: {}", e))?;
|
||||
|
||||
// Get the registration object, which drops our connection when it is dropped
|
||||
let _registration = response
|
||||
.get_registration()
|
||||
.map_err(|e| format!("failed to get registration object: {}", e))?;
|
||||
// Get the registration object, which drops our connection when it is dropped
|
||||
let registration = response
|
||||
.get_registration()
|
||||
.map_err(|e| format!("failed to get registration object: {}", e))?;
|
||||
|
||||
// Get the initial veilid state
|
||||
let veilid_state = response
|
||||
.get_state()
|
||||
.map_err(|e| format!("failed to get initial veilid state: {}", e))?;
|
||||
// Get the initial veilid state
|
||||
let veilid_state = response
|
||||
.get_state()
|
||||
.map_err(|e| format!("failed to get initial veilid state: {}", e))?;
|
||||
|
||||
// Set up our state for the first time
|
||||
let veilid_state: VeilidState = deserialize_json(veilid_state)
|
||||
.map_err(|e| format!("failed to get deserialize veilid state: {}", e))?;
|
||||
self.process_veilid_state(veilid_state).await?;
|
||||
// Set up our state for the first time
|
||||
let veilid_state: VeilidState = deserialize_json(veilid_state)
|
||||
.map_err(|e| format!("failed to get deserialize veilid state: {}", e))?;
|
||||
self.process_veilid_state(veilid_state).await?;
|
||||
|
||||
// Save server settings
|
||||
let server_settings = response
|
||||
.get_settings()
|
||||
.map_err(|e| format!("failed to get initial veilid server settings: {}", e))?
|
||||
.to_owned();
|
||||
self.inner.borrow_mut().server_settings = Some(server_settings.clone());
|
||||
// Save server settings
|
||||
let server_settings = response
|
||||
.get_settings()
|
||||
.map_err(|e| format!("failed to get initial veilid server settings: {}", e))?
|
||||
.to_owned();
|
||||
self.inner.borrow_mut().server_settings = Some(server_settings.clone());
|
||||
|
||||
// Don't drop the registration, doing so will remove the client
|
||||
// object mapping from the server which we need for the update backchannel
|
||||
// Don't drop the registration, doing so will remove the client
|
||||
// object mapping from the server which we need for the update backchannel
|
||||
Ok(registration)
|
||||
})
|
||||
.await;
|
||||
|
||||
let _registration = match reg_res {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
rpc_jh.abort().await;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait until rpc system completion or disconnect was requested
|
||||
let res = rpc_jh.await;
|
||||
// #[cfg(feature = "rt-tokio")]
|
||||
// let res = res.map_err(|e| format!("join error: {}", e))?;
|
||||
res.map_err(|e| format!("client RPC system error: {}", e))
|
||||
}
|
||||
|
||||
|
@ -424,6 +424,10 @@ reply - reply to an AppCall not handled directly by the server
|
||||
self.inner().ui.add_node_event(out);
|
||||
}
|
||||
}
|
||||
pub fn update_value_change(&mut self, value_change: veilid_core::VeilidValueChange) {
|
||||
let out = format!("Value change: {:?}", value_change);
|
||||
self.inner().ui.add_node_event(out);
|
||||
}
|
||||
|
||||
pub fn update_log(&mut self, log: veilid_core::VeilidLog) {
|
||||
self.inner().ui.add_node_event(format!(
|
||||
|
@ -50,7 +50,11 @@ fn format_bps(bps: ByteCount) -> String {
|
||||
impl TableViewItem<PeerTableColumn> for PeerTableData {
|
||||
fn to_column(&self, column: PeerTableColumn) -> String {
|
||||
match column {
|
||||
PeerTableColumn::NodeId => self.node_id.encode(),
|
||||
PeerTableColumn::NodeId => self
|
||||
.node_ids
|
||||
.best()
|
||||
.map(|n| n.value.encode())
|
||||
.unwrap_or_else(|| "???".to_owned()),
|
||||
PeerTableColumn::Address => format!(
|
||||
"{:?}:{}",
|
||||
self.peer_address.protocol_type(),
|
||||
@ -74,7 +78,21 @@ impl TableViewItem<PeerTableColumn> for PeerTableData {
|
||||
Self: Sized,
|
||||
{
|
||||
match column {
|
||||
PeerTableColumn::NodeId => self.node_id.cmp(&other.node_id),
|
||||
PeerTableColumn::NodeId => {
|
||||
let n1 = self
|
||||
.node_ids
|
||||
.best()
|
||||
.map(|n| n.value.encode())
|
||||
.unwrap_or_else(|| "???".to_owned());
|
||||
|
||||
let n2 = other
|
||||
.node_ids
|
||||
.best()
|
||||
.map(|n| n.value.encode())
|
||||
.unwrap_or_else(|| "???".to_owned());
|
||||
|
||||
n1.cmp(&n2)
|
||||
}
|
||||
PeerTableColumn::Address => self.to_column(column).cmp(&other.to_column(column)),
|
||||
PeerTableColumn::LatencyAvg => self
|
||||
.peer_stats
|
||||
|
@ -881,13 +881,11 @@ impl UI {
|
||||
}
|
||||
pub fn set_config(&mut self, config: VeilidConfigInner) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.ui_state.node_id.set(
|
||||
config
|
||||
.network
|
||||
.node_id
|
||||
.map(|x| x.encode())
|
||||
.unwrap_or("<unknown>".to_owned()),
|
||||
);
|
||||
|
||||
inner
|
||||
.ui_state
|
||||
.node_id
|
||||
.set(config.network.routing_table.node_id.to_string());
|
||||
}
|
||||
pub fn set_connection_state(&mut self, state: ConnectionState) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
@ -10,12 +10,14 @@ license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)"
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
rt-async-std = [ "async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std" ]
|
||||
rt-tokio = [ "tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket", "veilid-tools/rt-tokio" ]
|
||||
default = [ "enable-crypto-vld0" ]
|
||||
enable-crypto-vld0 = []
|
||||
enable-crypto-none = []
|
||||
rt-async-std = ["async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std"]
|
||||
rt-tokio = ["tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket", "veilid-tools/rt-tokio"]
|
||||
|
||||
veilid_core_android_tests = [ "dep:paranoid-android" ]
|
||||
veilid_core_ios_tests = [ "dep:tracing-oslog" ]
|
||||
veilid_core_android_tests = ["dep:paranoid-android"]
|
||||
veilid_core_ios_tests = ["dep:tracing-oslog"]
|
||||
tracking = []
|
||||
|
||||
[dependencies]
|
||||
@ -55,8 +57,8 @@ curve25519-dalek = { package = "curve25519-dalek-ng", version = "^4", default_fe
|
||||
# ed25519-dalek needs rand 0.7 until it updates itself
|
||||
rand = "0.7"
|
||||
# curve25519-dalek-ng is stuck on digest 0.9.0
|
||||
blake3 = { version = "1.1.0", default_features = false }
|
||||
digest = "0.9.0"
|
||||
blake3 = { version = "1.1.0" }
|
||||
rtnetlink = { version = "^0", default-features = false, optional = true }
|
||||
async-std-resolver = { version = "^0", optional = true }
|
||||
trust-dns-resolver = { version = "^0", optional = true }
|
||||
@ -65,6 +67,7 @@ keyvaluedb = { path = "../external/keyvaluedb/keyvaluedb" }
|
||||
rkyv = { git = "https://github.com/rkyv/rkyv.git", rev = "57e2a8d", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] }
|
||||
bytecheck = "^0"
|
||||
data-encoding = { version = "^2" }
|
||||
weak-table = "0.3.2"
|
||||
|
||||
# Dependencies for native builds only
|
||||
# Linux, Windows, Mac, iOS, Android
|
||||
|
@ -27,13 +27,24 @@ struct Nonce24 @0xb6260db25d8d7dfc {
|
||||
u2 @2 :UInt64;
|
||||
}
|
||||
|
||||
using NodeID = Key256;
|
||||
using RoutePublicKey = Key256;
|
||||
using ValueID = Key256;
|
||||
using Nonce = Nonce24;
|
||||
using Signature = Signature512;
|
||||
using BlockID = Key256;
|
||||
using TunnelID = UInt64;
|
||||
using PublicKey = Key256; # Node id / DHT key / Route id, etc
|
||||
using Nonce = Nonce24; # One-time encryption nonce
|
||||
using Signature = Signature512; # Signature block
|
||||
using TunnelID = UInt64; # Id for tunnels
|
||||
using CryptoKind = UInt32; # FOURCC code for cryptography type
|
||||
using ValueSeqNum = UInt32; # sequence numbers for values
|
||||
using ValueSchema = UInt32; # FOURCC code for schema (0 = freeform, SUB0 = subkey control v0)
|
||||
using Subkey = UInt32; # subkey index for dht
|
||||
|
||||
struct TypedKey @0xe2d567a9f1e61b29 {
|
||||
kind @0 :CryptoKind;
|
||||
key @1 :PublicKey;
|
||||
}
|
||||
|
||||
struct TypedSignature @0x963170c7298e3884 {
|
||||
kind @0 :CryptoKind;
|
||||
signature @1 :Signature;
|
||||
}
|
||||
|
||||
# Node Dial Info
|
||||
################################################################
|
||||
@ -123,7 +134,7 @@ struct RouteHopData @0x8ce231f9d1b7adf2 {
|
||||
|
||||
struct RouteHop @0xf8f672d75cce0c3b {
|
||||
node :union {
|
||||
nodeId @0 :NodeID; # node id only for established routes
|
||||
nodeId @0 :PublicKey; # node id key only for established routes (kind is the same as the pr or sr it is part of)
|
||||
peerInfo @1 :PeerInfo; # full peer info for this hop to establish the route
|
||||
}
|
||||
nextHop @2 :RouteHopData; # optional: If this the end of a private route, this field will not exist
|
||||
@ -131,7 +142,7 @@ struct RouteHop @0xf8f672d75cce0c3b {
|
||||
}
|
||||
|
||||
struct PrivateRoute @0x8a83fccb0851e776 {
|
||||
publicKey @0 :RoutePublicKey; # private route public key (unique per private route)
|
||||
publicKey @0 :TypedKey; # private route public key (unique per private route)
|
||||
hopCount @1 :UInt8; # Count of hops left in the private route (for timeout calculation purposes only)
|
||||
hops :union {
|
||||
firstHop @2 :RouteHop; # first hop of a private route is unencrypted (hopcount > 0)
|
||||
@ -141,7 +152,7 @@ struct PrivateRoute @0x8a83fccb0851e776 {
|
||||
}
|
||||
|
||||
struct SafetyRoute @0xf554734d07cb5d59 {
|
||||
publicKey @0 :RoutePublicKey; # safety route public key (unique per safety route)
|
||||
publicKey @0 :TypedKey; # safety route public key (unique per safety route)
|
||||
hopCount @1 :UInt8; # Count of hops left in the safety route (for timeout calculation purposes only)
|
||||
hops :union {
|
||||
data @2 :RouteHopData; # safety route has more hops
|
||||
@ -149,21 +160,6 @@ struct SafetyRoute @0xf554734d07cb5d59 {
|
||||
}
|
||||
}
|
||||
|
||||
# Values
|
||||
##############################
|
||||
|
||||
using ValueSeqNum = UInt32; # sequence numbers for values
|
||||
|
||||
struct ValueKey @0xe64b0992c21a0736 {
|
||||
publicKey @0 :ValueID; # the location of the value
|
||||
subkey @1 :Text; # the name of the subkey (or empty for the default subkey)
|
||||
}
|
||||
|
||||
struct ValueData @0xb4b7416f169f2a3d {
|
||||
seq @0 :ValueSeqNum; # sequence number of value
|
||||
data @1 :Data; # value or subvalue contents
|
||||
}
|
||||
|
||||
# Operations
|
||||
##############################
|
||||
|
||||
@ -234,23 +230,23 @@ struct NodeInfo @0xe125d847e3f9f419 {
|
||||
networkClass @0 :NetworkClass; # network class of this node
|
||||
outboundProtocols @1 :ProtocolTypeSet; # protocols that can go outbound
|
||||
addressTypes @2 :AddressTypeSet; # address types supported
|
||||
minVersion @3 :UInt8; # minimum protocol version for rpc
|
||||
maxVersion @4 :UInt8; # maximum protocol version for rpc
|
||||
envelopeSupport @3 :List(UInt8); # supported rpc envelope/receipt versions
|
||||
cryptoSupport @4 :List(CryptoKind); # cryptography systems supported
|
||||
dialInfoDetailList @5 :List(DialInfoDetail); # inbound dial info details for this node
|
||||
}
|
||||
|
||||
struct SignedDirectNodeInfo @0xe0e7ea3e893a3dd7 {
|
||||
nodeInfo @0 :NodeInfo; # node info
|
||||
timestamp @1 :UInt64; # when signed node info was generated
|
||||
signature @2 :Signature; # signature
|
||||
signatures @2 :List(TypedSignature); # signatures
|
||||
}
|
||||
|
||||
struct SignedRelayedNodeInfo @0xb39e8428ccd87cbb {
|
||||
nodeInfo @0 :NodeInfo; # node info
|
||||
relayId @1 :NodeID; # node id for relay
|
||||
relayIds @1 :List(TypedKey); # node ids for relay
|
||||
relayInfo @2 :SignedDirectNodeInfo; # signed node info for relay
|
||||
timestamp @3 :UInt64; # when signed node info was generated
|
||||
signature @4 :Signature; # signature
|
||||
signatures @4 :List(TypedSignature); # signatures
|
||||
}
|
||||
|
||||
struct SignedNodeInfo @0xd2478ce5f593406a {
|
||||
@ -261,16 +257,15 @@ struct SignedNodeInfo @0xd2478ce5f593406a {
|
||||
}
|
||||
|
||||
struct PeerInfo @0xfe2d722d5d3c4bcb {
|
||||
nodeId @0 :NodeID; # node id for 'closer peer'
|
||||
nodeIds @0 :List(TypedKey); # node ids for 'closer peer'
|
||||
signedNodeInfo @1 :SignedNodeInfo; # signed node info for 'closer peer'
|
||||
}
|
||||
|
||||
struct RoutedOperation @0xcbcb8535b839e9dd {
|
||||
version @0 :UInt8; # crypto version in use for the data
|
||||
sequencing @1 :Sequencing; # sequencing preference to use to pass the message along
|
||||
signatures @2 :List(Signature); # signatures from nodes that have handled the private route
|
||||
nonce @3 :Nonce; # nonce Xmsg
|
||||
data @4 :Data; # operation encrypted with ENC(Xmsg,DH(PKapr,SKbsr))
|
||||
sequencing @0 :Sequencing; # sequencing preference to use to pass the message along
|
||||
signatures @1 :List(Signature); # signatures from nodes that have handled the private route
|
||||
nonce @2 :Nonce; # nonce Xmsg
|
||||
data @3 :Data; # operation encrypted with ENC(Xmsg,DH(PKapr,SKbsr))
|
||||
}
|
||||
|
||||
struct OperationStatusQ @0x865d80cea70d884a {
|
||||
@ -293,7 +288,7 @@ struct OperationReturnReceipt @0xeb0fb5b5a9160eeb {
|
||||
}
|
||||
|
||||
struct OperationFindNodeQ @0xfdef788fe9623bcd {
|
||||
nodeId @0 :NodeID; # node id to locate
|
||||
nodeId @0 :TypedKey; # node id to locate
|
||||
}
|
||||
|
||||
struct OperationFindNodeA @0xa84cf2fb40c77089 {
|
||||
@ -301,24 +296,36 @@ struct OperationFindNodeA @0xa84cf2fb40c77089 {
|
||||
}
|
||||
|
||||
struct OperationRoute @0x96741859ce6ac7dd {
|
||||
safetyRoute @0 :SafetyRoute; # Where this should go
|
||||
operation @1 :RoutedOperation; # The operation to be routed
|
||||
safetyRoute @0 :SafetyRoute; # where this should go
|
||||
operation @1 :RoutedOperation; # the operation to be routed
|
||||
}
|
||||
|
||||
struct OperationAppCallQ @0xade67b9f09784507 {
|
||||
message @0 :Data; # Opaque request to application
|
||||
message @0 :Data; # opaque request to application
|
||||
}
|
||||
|
||||
struct OperationAppCallA @0xf7c797ac85f214b8 {
|
||||
message @0 :Data; # Opaque response from application
|
||||
message @0 :Data; # opaque response from application
|
||||
}
|
||||
|
||||
struct OperationAppMessage @0x9baf542d81b411f5 {
|
||||
message @0 :Data; # Opaque message to application
|
||||
message @0 :Data; # opaque message to application
|
||||
}
|
||||
|
||||
struct SubkeyRange {
|
||||
start @0 :Subkey; # the start of a subkey range
|
||||
end @1 :Subkey; # the end of a subkey range
|
||||
}
|
||||
|
||||
struct ValueData @0xb4b7416f169f2a3d {
|
||||
seq @0 :ValueSeqNum; # sequence number of value
|
||||
schema @1 :ValueSchema; # fourcc code of schema for value
|
||||
data @2 :Data; # value or subvalue contents
|
||||
}
|
||||
|
||||
struct OperationGetValueQ @0xf88a5b6da5eda5d0 {
|
||||
key @0 :ValueKey; # key for value to get
|
||||
key @0 :TypedKey; # the location of the value
|
||||
subkey @1 :Subkey; # the index of the subkey (0 for the default subkey)
|
||||
}
|
||||
|
||||
struct OperationGetValueA @0xd896bb46f2e0249f {
|
||||
@ -329,8 +336,9 @@ struct OperationGetValueA @0xd896bb46f2e0249f {
|
||||
}
|
||||
|
||||
struct OperationSetValueQ @0xbac06191ff8bdbc5 {
|
||||
key @0 :ValueKey; # key for value to update
|
||||
value @1 :ValueData; # value or subvalue contents (older or equal seq number gets dropped)
|
||||
key @0 :TypedKey; # the location of the value
|
||||
subkey @1 :Subkey; # the index of the subkey (0 for the default subkey)
|
||||
value @2 :ValueData; # value or subvalue contents (older or equal seq number gets dropped)
|
||||
}
|
||||
|
||||
struct OperationSetValueA @0x9378d0732dc95be2 {
|
||||
@ -341,7 +349,10 @@ struct OperationSetValueA @0x9378d0732dc95be2 {
|
||||
}
|
||||
|
||||
struct OperationWatchValueQ @0xf9a5a6c547b9b228 {
|
||||
key @0 :ValueKey; # key for value to watch
|
||||
key @0 :TypedKey; # key for value to watch
|
||||
subkeys @1 :List(SubkeyRange); # subkey range to watch, if empty, watch everything
|
||||
expiration @2 :UInt64; # requested timestamp when this watch will expire in usec since epoch (can be return less, 0 for max)
|
||||
count @3 :UInt32; # requested number of changes to watch for (0 = cancel, 1 = single shot, 2+ = counter, UINT32_MAX = continuous)
|
||||
}
|
||||
|
||||
struct OperationWatchValueA @0xa726cab7064ba893 {
|
||||
@ -350,12 +361,14 @@ struct OperationWatchValueA @0xa726cab7064ba893 {
|
||||
}
|
||||
|
||||
struct OperationValueChanged @0xd1c59ebdd8cc1bf6 {
|
||||
key @0 :ValueKey; # key for value that changed
|
||||
value @1 :ValueData; # value or subvalue contents with sequence number
|
||||
key @0 :TypedKey; # key for value that changed
|
||||
subkeys @1 :List(SubkeyRange); # subkey range that changed (up to 512 ranges at a time)
|
||||
count @2 :UInt32; # remaining changes left (0 means watch has expired)
|
||||
value @3 :ValueData; # first value that changed (the rest can be gotten with getvalue)
|
||||
}
|
||||
|
||||
struct OperationSupplyBlockQ @0xadbf4c542d749971 {
|
||||
blockId @0 :BlockID; # hash of the block we can supply
|
||||
blockId @0 :TypedKey; # hash of the block we can supply
|
||||
}
|
||||
|
||||
struct OperationSupplyBlockA @0xf003822e83b5c0d7 {
|
||||
@ -366,7 +379,7 @@ struct OperationSupplyBlockA @0xf003822e83b5c0d7 {
|
||||
}
|
||||
|
||||
struct OperationFindBlockQ @0xaf4353ff004c7156 {
|
||||
blockId @0 :BlockID; # hash of the block to locate
|
||||
blockId @0 :TypedKey; # hash of the block to locate
|
||||
}
|
||||
|
||||
struct OperationFindBlockA @0xc51455bc4915465d {
|
||||
@ -516,7 +529,7 @@ struct Answer @0xacacb8b6988c1058 {
|
||||
|
||||
struct Operation @0xbf2811c435403c3b {
|
||||
opId @0 :UInt64; # Random RPC ID. Must be random to foil reply forgery attacks.
|
||||
senderNodeInfo @1 :SignedNodeInfo; # (optional) SignedNodeInfo for the sender to be cached by the receiver.
|
||||
senderPeerInfo @1 :PeerInfo; # (optional) PeerInfo for the sender to be cached by the receiver.
|
||||
targetNodeInfoTs @2 :UInt64; # Timestamp the sender believes the target's node info to be at or zero if not sent
|
||||
kind :union {
|
||||
question @3 :Question;
|
||||
|
@ -67,12 +67,6 @@ impl ServicesContext {
|
||||
}
|
||||
self.protected_store = Some(protected_store.clone());
|
||||
|
||||
// Init node id from config now that protected store is set up
|
||||
if let Err(e) = self.config.init_node_id(protected_store.clone()).await {
|
||||
self.shutdown().await;
|
||||
return Err(e).wrap_err("init node id failed");
|
||||
}
|
||||
|
||||
// Set up tablestore
|
||||
trace!("init table store");
|
||||
let table_store = TableStore::new(self.config.clone());
|
||||
@ -84,7 +78,11 @@ impl ServicesContext {
|
||||
|
||||
// Set up crypto
|
||||
trace!("init crypto");
|
||||
let crypto = Crypto::new(self.config.clone(), table_store.clone());
|
||||
let crypto = Crypto::new(
|
||||
self.config.clone(),
|
||||
table_store.clone(),
|
||||
protected_store.clone(),
|
||||
);
|
||||
if let Err(e) = crypto.init().await {
|
||||
self.shutdown().await;
|
||||
return Err(e);
|
||||
|
266
veilid-core/src/crypto/byte_array_types.rs
Normal file
266
veilid-core/src/crypto/byte_array_types.rs
Normal file
@ -0,0 +1,266 @@
|
||||
use super::*;
|
||||
|
||||
use core::cmp::{Eq, Ord, PartialEq, PartialOrd};
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::fmt;
|
||||
use core::hash::Hash;
|
||||
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
|
||||
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Length of a public key in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
/// Length of a public key in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const PUBLIC_KEY_LENGTH_ENCODED: usize = 43;
|
||||
/// Length of a secret key in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const SECRET_KEY_LENGTH: usize = 32;
|
||||
/// Length of a secret key in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const SECRET_KEY_LENGTH_ENCODED: usize = 43;
|
||||
/// Length of a signature in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const SIGNATURE_LENGTH: usize = 64;
|
||||
/// Length of a signature in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const SIGNATURE_LENGTH_ENCODED: usize = 86;
|
||||
/// Length of a nonce in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const NONCE_LENGTH: usize = 24;
|
||||
/// Length of a nonce in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const NONCE_LENGTH_ENCODED: usize = 32;
|
||||
/// Length of a shared secret in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const SHARED_SECRET_LENGTH: usize = 32;
|
||||
/// Length of a shared secret in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const SHARED_SECRET_LENGTH_ENCODED: usize = 43;
|
||||
/// Length of a route id in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const ROUTE_ID_LENGTH: usize = 32;
|
||||
/// Length of a route id in bytes afer encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const ROUTE_ID_LENGTH_ENCODED: usize = 43;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub trait Encodable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn encode(&self) -> String;
|
||||
fn encoded_len() -> usize;
|
||||
fn try_decode<S: AsRef<str>>(input: S) -> Result<Self, VeilidAPIError> {
|
||||
let b = input.as_ref().as_bytes();
|
||||
Self::try_decode_bytes(b)
|
||||
}
|
||||
fn try_decode_bytes(b: &[u8]) -> Result<Self, VeilidAPIError>;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
macro_rules! byte_array_type {
|
||||
($name:ident, $size:expr, $encoded_size:expr) => {
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes, Hash, Eq, PartialEq, PartialOrd, Ord))]
|
||||
pub struct $name {
|
||||
pub bytes: [u8; $size],
|
||||
}
|
||||
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bytes: [0u8; $size],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let s = self.encode();
|
||||
serde::Serialize::serialize(&s, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
||||
if s == "" {
|
||||
return Ok($name::default());
|
||||
}
|
||||
$name::try_decode(s.as_str()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn new(bytes: [u8; $size]) -> Self {
|
||||
Self { bytes }
|
||||
}
|
||||
|
||||
pub fn try_from_vec(v: Vec<u8>) -> Result<Self, VeilidAPIError> {
|
||||
let vl = v.len();
|
||||
Ok(Self {
|
||||
bytes: v.try_into().map_err(|_| {
|
||||
VeilidAPIError::generic(format!(
|
||||
"Expected a Vec of length {} but it was {}",
|
||||
$size, vl
|
||||
))
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bit(&self, index: usize) -> bool {
|
||||
assert!(index < ($size * 8));
|
||||
let bi = index / 8;
|
||||
let ti = 7 - (index % 8);
|
||||
((self.bytes[bi] >> ti) & 1) != 0
|
||||
}
|
||||
|
||||
pub fn first_nonzero_bit(&self) -> Option<usize> {
|
||||
for i in 0..$size {
|
||||
let b = self.bytes[i];
|
||||
if b != 0 {
|
||||
for n in 0..8 {
|
||||
if ((b >> (7 - n)) & 1u8) != 0u8 {
|
||||
return Some((i * 8) + n);
|
||||
}
|
||||
}
|
||||
panic!("wtf")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn nibble(&self, index: usize) -> u8 {
|
||||
assert!(index < ($size * 2));
|
||||
let bi = index / 2;
|
||||
if index & 1 == 0 {
|
||||
(self.bytes[bi] >> 4) & 0xFu8
|
||||
} else {
|
||||
self.bytes[bi] & 0xFu8
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_nonzero_nibble(&self) -> Option<(usize, u8)> {
|
||||
for i in 0..($size * 2) {
|
||||
let n = self.nibble(i);
|
||||
if n != 0 {
|
||||
return Some((i, n));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for $name {
|
||||
fn encode(&self) -> String {
|
||||
BASE64URL_NOPAD.encode(&self.bytes)
|
||||
}
|
||||
fn encoded_len() -> usize {
|
||||
$encoded_size
|
||||
}
|
||||
fn try_decode_bytes(b: &[u8]) -> Result<Self, VeilidAPIError> {
|
||||
let mut bytes = [0u8; $size];
|
||||
let res = BASE64URL_NOPAD.decode_len(b.len());
|
||||
match res {
|
||||
Ok(v) => {
|
||||
if v != $size {
|
||||
apibail_generic!("Incorrect length in decode");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
apibail_generic!("Failed to decode");
|
||||
}
|
||||
}
|
||||
|
||||
let res = BASE64URL_NOPAD.decode_mut(b, &mut bytes);
|
||||
match res {
|
||||
Ok(_) => Ok(Self::new(bytes)),
|
||||
Err(_) => apibail_generic!("Failed to decode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, concat!(stringify!($name), "("))?;
|
||||
write!(f, "{}", self.encode())?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&$name> for String {
|
||||
fn from(value: &$name) -> Self {
|
||||
value.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = VeilidAPIError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
$name::try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for $name {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
$name::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for $name {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Self::try_decode(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
||||
byte_array_type!(PublicKey, PUBLIC_KEY_LENGTH, PUBLIC_KEY_LENGTH_ENCODED);
|
||||
byte_array_type!(SecretKey, SECRET_KEY_LENGTH, SECRET_KEY_LENGTH_ENCODED);
|
||||
byte_array_type!(Signature, SIGNATURE_LENGTH, SIGNATURE_LENGTH_ENCODED);
|
||||
byte_array_type!(
|
||||
PublicKeyDistance,
|
||||
PUBLIC_KEY_LENGTH,
|
||||
PUBLIC_KEY_LENGTH_ENCODED
|
||||
);
|
||||
byte_array_type!(Nonce, NONCE_LENGTH, NONCE_LENGTH_ENCODED);
|
||||
byte_array_type!(
|
||||
SharedSecret,
|
||||
SHARED_SECRET_LENGTH,
|
||||
SHARED_SECRET_LENGTH_ENCODED
|
||||
);
|
||||
byte_array_type!(RouteId, ROUTE_ID_LENGTH, ROUTE_ID_LENGTH_ENCODED);
|
113
veilid-core/src/crypto/crypto_system.rs
Normal file
113
veilid-core/src/crypto/crypto_system.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use super::*;
|
||||
|
||||
pub trait CryptoSystem {
|
||||
// Accessors
|
||||
fn kind(&self) -> CryptoKind;
|
||||
fn crypto(&self) -> Crypto;
|
||||
|
||||
// Cached Operations
|
||||
fn cached_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError>;
|
||||
|
||||
// Generation
|
||||
fn random_nonce(&self) -> Nonce;
|
||||
fn random_shared_secret(&self) -> SharedSecret;
|
||||
fn compute_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError>;
|
||||
fn generate_keypair(&self) -> KeyPair;
|
||||
fn generate_hash(&self, data: &[u8]) -> PublicKey;
|
||||
fn generate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
) -> Result<PublicKey, VeilidAPIError>;
|
||||
|
||||
// Validation
|
||||
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool;
|
||||
fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool;
|
||||
fn validate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
key: &PublicKey,
|
||||
) -> Result<bool, VeilidAPIError>;
|
||||
|
||||
// Distance Metric
|
||||
fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> PublicKeyDistance;
|
||||
|
||||
// Authentication
|
||||
fn sign(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
data: &[u8],
|
||||
) -> Result<Signature, VeilidAPIError>;
|
||||
fn verify(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
data: &[u8],
|
||||
signature: &Signature,
|
||||
) -> Result<(), VeilidAPIError>;
|
||||
|
||||
// AEAD Encrypt/Decrypt
|
||||
fn aead_overhead(&self) -> usize;
|
||||
fn decrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError>;
|
||||
fn decrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError>;
|
||||
fn encrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError>;
|
||||
fn encrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError>;
|
||||
|
||||
// NoAuth Encrypt/Decrypt
|
||||
fn crypt_in_place_no_auth(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
);
|
||||
fn crypt_b2b_no_auth(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
out_buf: &mut [u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
);
|
||||
fn crypt_no_auth_aligned_8(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8>;
|
||||
fn crypt_no_auth_unaligned(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8>;
|
||||
}
|
45
veilid-core/src/crypto/dh_cache.rs
Normal file
45
veilid-core/src/crypto/dh_cache.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
// Diffie-Hellman key exchange cache
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct DHCacheKey {
|
||||
pub key: PublicKey,
|
||||
pub secret: SecretKey,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DHCacheValue {
|
||||
pub shared_secret: SharedSecret,
|
||||
}
|
||||
|
||||
pub type DHCache = LruCache<DHCacheKey, DHCacheValue>;
|
||||
pub const DH_CACHE_SIZE: usize = 4096;
|
||||
|
||||
pub fn cache_to_bytes(cache: &DHCache) -> Vec<u8> {
|
||||
let cnt: usize = cache.len();
|
||||
let mut out: Vec<u8> = Vec::with_capacity(cnt * (32 + 32 + 32));
|
||||
for e in cache.iter() {
|
||||
out.extend(&e.0.key.bytes);
|
||||
out.extend(&e.0.secret.bytes);
|
||||
out.extend(&e.1.shared_secret.bytes);
|
||||
}
|
||||
let mut rev: Vec<u8> = Vec::with_capacity(out.len());
|
||||
for d in out.chunks(32 + 32 + 32).rev() {
|
||||
rev.extend(d);
|
||||
}
|
||||
rev
|
||||
}
|
||||
|
||||
pub fn bytes_to_cache(bytes: &[u8], cache: &mut DHCache) {
|
||||
for d in bytes.chunks(32 + 32 + 32) {
|
||||
let k = DHCacheKey {
|
||||
key: PublicKey::new(d[0..32].try_into().expect("asdf")),
|
||||
secret: SecretKey::new(d[32..64].try_into().expect("asdf")),
|
||||
};
|
||||
let v = DHCacheValue {
|
||||
shared_secret: SharedSecret::new(d[64..96].try_into().expect("asdf")),
|
||||
};
|
||||
cache.insert(k, v, |_k, _v| {});
|
||||
}
|
||||
}
|
@ -1,69 +1,64 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::absurd_extreme_comparisons)]
|
||||
use super::*;
|
||||
use crate::routing_table::VersionRange;
|
||||
use crate::*;
|
||||
use core::convert::TryInto;
|
||||
|
||||
// #[repr(C, packed)]
|
||||
// struct EnvelopeHeader {
|
||||
// // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
|
||||
// magic: [u8; 4], // 0x00: 0x56 0x4C 0x49 0x44 ("VLID")
|
||||
// version: u8, // 0x04: 0 = EnvelopeV0
|
||||
// min_version: u8, // 0x05: 0 = EnvelopeV0
|
||||
// max_version: u8, // 0x06: 0 = EnvelopeV0
|
||||
// reserved: u8, // 0x07: Reserved for future use
|
||||
// }
|
||||
|
||||
// #[repr(C, packed)]
|
||||
// struct EnvelopeV0 {
|
||||
// // Size is 106 bytes.
|
||||
// magic: [u8; 4], // 0x00: 0x56 0x4C 0x49 0x44 ("VLID")
|
||||
// version: u8, // 0x04: 0 = EnvelopeV0
|
||||
// min_version: u8, // 0x05: 0 = EnvelopeV0
|
||||
// max_version: u8, // 0x06: 0 = EnvelopeV0
|
||||
// reserved: u8, // 0x07: Reserved for future use
|
||||
// size: u16, // 0x08: Total size of the envelope including the encrypted operations message. Maximum size is 65,507 bytes, which is the data size limit for a single UDP message on IPv4.
|
||||
// timestamp: u64, // 0x0A: Duration since UNIX_EPOCH in microseconds when this message is sent. Messages older than 10 seconds are dropped.
|
||||
// nonce: [u8; 24], // 0x12: Random nonce for replay protection and for x25519
|
||||
// sender_id: [u8; 32], // 0x2A: Node ID of the message source, which is the Ed25519 public key of the sender (must be verified with find_node if this is a new node_id/address combination)
|
||||
// recipient_id: [u8; 32], // 0x4A: Node ID of the intended recipient, which is the Ed25519 public key of the recipient (must be the receiving node, or a relay lease holder)
|
||||
// // 0x6A: message is appended (operations)
|
||||
// // encrypted by XChaCha20Poly1305(nonce,x25519(recipient_id, sender_secret_key))
|
||||
// signature: [u8; 64], // 0x?? (end-0x40): Ed25519 signature of the entire envelope including header is appended to the packet
|
||||
// // entire header needs to be included in message digest, relays are not allowed to modify the envelope without invalidating the signature.
|
||||
// }
|
||||
/// Envelopes are versioned
|
||||
///
|
||||
/// These are the formats for the on-the-wire serialization performed by this module
|
||||
///
|
||||
/// #[repr(C, packed)]
|
||||
/// struct EnvelopeHeader {
|
||||
/// // Size is at least 4 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
|
||||
/// magic: [u8; 3], // 0x00: 0x56 0x4C 0x44 ("VLD")
|
||||
/// version: u8, // 0x03: 0 = EnvelopeV0
|
||||
/// }
|
||||
///
|
||||
/// #[repr(C, packed)]
|
||||
/// struct EnvelopeV0 {
|
||||
/// // Size is 106 bytes without signature and 170 with signature
|
||||
/// magic: [u8; 3], // 0x00: 0x56 0x4C 0x44 ("VLD")
|
||||
/// version: u8, // 0x03: 0 = EnvelopeV0
|
||||
/// crypto_kind: [u8; 4], // 0x04: CryptoSystemVersion FOURCC code (CryptoKind)
|
||||
/// size: u16, // 0x08: Total size of the envelope including the encrypted operations message. Maximum size is 65,507 bytes, which is the data size limit for a single UDP message on IPv4.
|
||||
/// timestamp: u64, // 0x0A: Duration since UNIX_EPOCH in microseconds when this message is sent. Messages older than 10 seconds are dropped.
|
||||
/// nonce: [u8; 24], // 0x12: Random nonce for replay protection and for dh
|
||||
/// sender_id: [u8; 32], // 0x2A: Node ID of the message source, which is the public key of the sender (must be verified with find_node if this is a new node_id/address combination)
|
||||
/// recipient_id: [u8; 32], // 0x4A: Node ID of the intended recipient, which is the public key of the recipient (must be the receiving node, or a relay lease holder)
|
||||
/// // 0x6A: message is appended (operations)
|
||||
/// signature: [u8; 64], // 0x?? (end-0x40): Signature of the entire envelope including header is appended to the packet
|
||||
/// // entire header needs to be included in message digest, relays are not allowed to modify the envelope without invalidating the signature.
|
||||
/// }
|
||||
|
||||
pub const MAX_ENVELOPE_SIZE: usize = 65507;
|
||||
pub const MIN_ENVELOPE_SIZE: usize = 0x6A + 0x40; // Header + Signature
|
||||
pub const ENVELOPE_MAGIC: &[u8; 4] = b"VLID";
|
||||
pub type EnvelopeNonce = [u8; 24];
|
||||
pub const ENVELOPE_MAGIC: &[u8; 3] = b"VLD";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Envelope {
|
||||
version: u8,
|
||||
min_version: u8,
|
||||
max_version: u8,
|
||||
version: EnvelopeVersion,
|
||||
crypto_kind: CryptoKind,
|
||||
timestamp: Timestamp,
|
||||
nonce: EnvelopeNonce,
|
||||
sender_id: DHTKey,
|
||||
recipient_id: DHTKey,
|
||||
nonce: Nonce,
|
||||
sender_id: PublicKey,
|
||||
recipient_id: PublicKey,
|
||||
}
|
||||
|
||||
impl Envelope {
|
||||
pub fn new(
|
||||
version: u8,
|
||||
version: EnvelopeVersion,
|
||||
crypto_kind: CryptoKind,
|
||||
timestamp: Timestamp,
|
||||
nonce: EnvelopeNonce,
|
||||
sender_id: DHTKey,
|
||||
recipient_id: DHTKey,
|
||||
nonce: Nonce,
|
||||
sender_id: PublicKey,
|
||||
recipient_id: PublicKey,
|
||||
) -> Self {
|
||||
assert!(version >= MIN_CRYPTO_VERSION);
|
||||
assert!(version <= MAX_CRYPTO_VERSION);
|
||||
assert!(VALID_ENVELOPE_VERSIONS.contains(&version));
|
||||
assert!(VALID_CRYPTO_KINDS.contains(&crypto_kind));
|
||||
Self {
|
||||
version,
|
||||
min_version: MIN_CRYPTO_VERSION,
|
||||
max_version: MAX_CRYPTO_VERSION,
|
||||
crypto_kind,
|
||||
timestamp,
|
||||
nonce,
|
||||
sender_id,
|
||||
@ -71,7 +66,7 @@ impl Envelope {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_signed_data(data: &[u8]) -> Result<Envelope, VeilidAPIError> {
|
||||
pub fn from_signed_data(crypto: Crypto, data: &[u8]) -> Result<Envelope, VeilidAPIError> {
|
||||
// Ensure we are at least the length of the envelope
|
||||
// Silent drop here, as we use zero length packets as part of the protocol for hole punching
|
||||
if data.len() < MIN_ENVELOPE_SIZE {
|
||||
@ -79,33 +74,28 @@ impl Envelope {
|
||||
}
|
||||
|
||||
// Verify magic number
|
||||
let magic: [u8; 4] = data[0x00..0x04]
|
||||
let magic: [u8; 3] = data[0x00..0x03]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
if magic != *ENVELOPE_MAGIC {
|
||||
apibail_generic!("bad magic number");
|
||||
}
|
||||
|
||||
// Check version
|
||||
let version = data[0x04];
|
||||
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
|
||||
apibail_parse_error!("unsupported cryptography version", version);
|
||||
// Check envelope version
|
||||
let version = data[0x03];
|
||||
if !VALID_ENVELOPE_VERSIONS.contains(&version) {
|
||||
apibail_parse_error!("unsupported envelope version", version);
|
||||
}
|
||||
|
||||
// Get min version
|
||||
let min_version = data[0x05];
|
||||
if min_version > version {
|
||||
apibail_parse_error!("version too low", version);
|
||||
}
|
||||
|
||||
// Get max version
|
||||
let max_version = data[0x06];
|
||||
if version > max_version {
|
||||
apibail_parse_error!("version too high", version);
|
||||
}
|
||||
if min_version > max_version {
|
||||
apibail_generic!("version information invalid");
|
||||
}
|
||||
// Check crypto kind
|
||||
let crypto_kind = FourCC(
|
||||
data[0x04..0x08]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
let Some(vcrypto) = crypto.get(crypto_kind) else {
|
||||
apibail_parse_error!("unsupported crypto kind", crypto_kind);
|
||||
};
|
||||
|
||||
// Get size and ensure it matches the size of the envelope and is less than the maximum message size
|
||||
let size: u16 = u16::from_le_bytes(
|
||||
@ -136,17 +126,18 @@ impl Envelope {
|
||||
.into();
|
||||
|
||||
// Get nonce and sender node id
|
||||
let nonce: EnvelopeNonce = data[0x12..0x2A]
|
||||
let nonce_slice: [u8; NONCE_LENGTH] = data[0x12..0x2A]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
let sender_id_slice: [u8; 32] = data[0x2A..0x4A]
|
||||
let sender_id_slice: [u8; PUBLIC_KEY_LENGTH] = data[0x2A..0x4A]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
let recipient_id_slice: [u8; 32] = data[0x4A..0x6A]
|
||||
let recipient_id_slice: [u8; PUBLIC_KEY_LENGTH] = data[0x4A..0x6A]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
let sender_id = DHTKey::new(sender_id_slice);
|
||||
let recipient_id = DHTKey::new(recipient_id_slice);
|
||||
let nonce: Nonce = Nonce::new(nonce_slice);
|
||||
let sender_id = PublicKey::new(sender_id_slice);
|
||||
let recipient_id = PublicKey::new(recipient_id_slice);
|
||||
|
||||
// Ensure sender_id and recipient_id are not the same
|
||||
if sender_id == recipient_id {
|
||||
@ -157,21 +148,21 @@ impl Envelope {
|
||||
}
|
||||
|
||||
// Get signature
|
||||
let signature = DHTSignature::new(
|
||||
let signature = Signature::new(
|
||||
data[(data.len() - 64)..]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
|
||||
// Validate signature
|
||||
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
|
||||
vcrypto
|
||||
.verify(&sender_id, &data[0..(data.len() - 64)], &signature)
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
|
||||
// Return envelope
|
||||
Ok(Self {
|
||||
version,
|
||||
min_version,
|
||||
max_version,
|
||||
crypto_kind,
|
||||
timestamp,
|
||||
nonce,
|
||||
sender_id,
|
||||
@ -183,13 +174,17 @@ impl Envelope {
|
||||
&self,
|
||||
crypto: Crypto,
|
||||
data: &[u8],
|
||||
node_id_secret: &DHTKeySecret,
|
||||
node_id_secret: &SecretKey,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
// Get DH secret
|
||||
let dh_secret = crypto.cached_dh(&self.sender_id, node_id_secret)?;
|
||||
let vcrypto = crypto
|
||||
.get(self.crypto_kind)
|
||||
.expect("need to ensure only valid crypto kinds here");
|
||||
let dh_secret = vcrypto.cached_dh(&self.sender_id, node_id_secret)?;
|
||||
|
||||
// Decrypt message without authentication
|
||||
let body = Crypto::crypt_no_auth(&data[0x6A..data.len() - 64], &self.nonce, &dh_secret);
|
||||
let body =
|
||||
vcrypto.crypt_no_auth_aligned_8(&data[0x6A..data.len() - 64], &self.nonce, &dh_secret);
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
@ -198,39 +193,41 @@ impl Envelope {
|
||||
&self,
|
||||
crypto: Crypto,
|
||||
body: &[u8],
|
||||
node_id_secret: &DHTKeySecret,
|
||||
node_id_secret: &SecretKey,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
// Ensure body isn't too long
|
||||
let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE;
|
||||
if envelope_size > MAX_ENVELOPE_SIZE {
|
||||
apibail_parse_error!("envelope size is too large", envelope_size);
|
||||
}
|
||||
// Generate dh secret
|
||||
let vcrypto = crypto
|
||||
.get(self.crypto_kind)
|
||||
.expect("need to ensure only valid crypto kinds here");
|
||||
let dh_secret = vcrypto.cached_dh(&self.recipient_id, node_id_secret)?;
|
||||
|
||||
// Write envelope body
|
||||
let mut data = vec![0u8; envelope_size];
|
||||
|
||||
// Write magic
|
||||
data[0x00..0x04].copy_from_slice(ENVELOPE_MAGIC);
|
||||
data[0x00..0x03].copy_from_slice(ENVELOPE_MAGIC);
|
||||
// Write version
|
||||
data[0x04] = self.version;
|
||||
// Write min version
|
||||
data[0x05] = self.min_version;
|
||||
// Write max version
|
||||
data[0x06] = self.max_version;
|
||||
data[0x03] = self.version;
|
||||
// Write crypto kind
|
||||
data[0x04..0x08].copy_from_slice(&self.crypto_kind.0);
|
||||
// Write size
|
||||
data[0x08..0x0A].copy_from_slice(&(envelope_size as u16).to_le_bytes());
|
||||
// Write timestamp
|
||||
data[0x0A..0x12].copy_from_slice(&self.timestamp.as_u64().to_le_bytes());
|
||||
// Write nonce
|
||||
data[0x12..0x2A].copy_from_slice(&self.nonce);
|
||||
data[0x12..0x2A].copy_from_slice(&self.nonce.bytes);
|
||||
// Write sender node id
|
||||
data[0x2A..0x4A].copy_from_slice(&self.sender_id.bytes);
|
||||
// Write recipient node id
|
||||
data[0x4A..0x6A].copy_from_slice(&self.recipient_id.bytes);
|
||||
|
||||
// Generate dh secret
|
||||
let dh_secret = crypto.cached_dh(&self.recipient_id, node_id_secret)?;
|
||||
|
||||
// Encrypt and authenticate message
|
||||
let encrypted_body = Crypto::crypt_no_auth(body, &self.nonce, &dh_secret);
|
||||
let encrypted_body = vcrypto.crypt_no_auth_unaligned(body, &self.nonce, &dh_secret);
|
||||
|
||||
// Write body
|
||||
if !encrypted_body.is_empty() {
|
||||
@ -238,7 +235,7 @@ impl Envelope {
|
||||
}
|
||||
|
||||
// Sign the envelope
|
||||
let signature = sign(
|
||||
let signature = vcrypto.sign(
|
||||
&self.sender_id,
|
||||
node_id_secret,
|
||||
&data[0..(envelope_size - 64)],
|
||||
@ -254,25 +251,23 @@ impl Envelope {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn get_min_max_version(&self) -> VersionRange {
|
||||
VersionRange {
|
||||
min: self.min_version,
|
||||
max: self.max_version,
|
||||
}
|
||||
pub fn get_crypto_kind(&self) -> CryptoKind {
|
||||
self.crypto_kind
|
||||
}
|
||||
|
||||
pub fn get_timestamp(&self) -> Timestamp {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
pub fn get_nonce(&self) -> EnvelopeNonce {
|
||||
pub fn get_nonce(&self) -> Nonce {
|
||||
self.nonce
|
||||
}
|
||||
|
||||
pub fn get_sender_id(&self) -> DHTKey {
|
||||
pub fn get_sender_id(&self) -> PublicKey {
|
||||
self.sender_id
|
||||
}
|
||||
pub fn get_recipient_id(&self) -> DHTKey {
|
||||
|
||||
pub fn get_recipient_id(&self) -> PublicKey {
|
||||
self.recipient_id
|
||||
}
|
||||
}
|
||||
|
@ -1,389 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
use core::cmp::{Eq, Ord, PartialEq, PartialOrd};
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::fmt;
|
||||
use core::hash::Hash;
|
||||
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use digest::generic_array::typenum::U64;
|
||||
use digest::{Digest, Output};
|
||||
use ed25519_dalek::{Keypair, PublicKey, Signature};
|
||||
use generic_array::GenericArray;
|
||||
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Length of a DHT key in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const DHT_KEY_LENGTH: usize = 32;
|
||||
/// Length of a DHT key in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const DHT_KEY_LENGTH_ENCODED: usize = 43;
|
||||
/// Length of a DHT secret in bytes
|
||||
#[allow(dead_code)]
|
||||
pub const DHT_KEY_SECRET_LENGTH: usize = 32;
|
||||
/// Length of a DHT secret in bytes after encoding to base64url
|
||||
#[allow(dead_code)]
|
||||
pub const DHT_KEY_SECRET_LENGTH_ENCODED: usize = 43;
|
||||
/// Length of a DHT signature in bytes
|
||||
#[allow(dead_code)]
|
||||
/// Length of a DHT signature in bytes after encoding to base64url
|
||||
pub const DHT_SIGNATURE_LENGTH: usize = 64;
|
||||
#[allow(dead_code)]
|
||||
pub const DHT_SIGNATURE_LENGTH_ENCODED: usize = 86;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
macro_rules! byte_array_type {
|
||||
($name:ident, $size:expr) => {
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes, Hash, Eq, PartialEq, PartialOrd, Ord))]
|
||||
pub struct $name {
|
||||
pub bytes: [u8; $size],
|
||||
}
|
||||
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bytes: [0u8; $size],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let s = self.encode();
|
||||
serde::Serialize::serialize(&s, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
||||
if s == "" {
|
||||
return Ok($name::default());
|
||||
}
|
||||
$name::try_decode(s.as_str()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn new(bytes: [u8; $size]) -> Self {
|
||||
Self { bytes }
|
||||
}
|
||||
|
||||
pub fn try_from_vec(v: Vec<u8>) -> Result<Self, VeilidAPIError> {
|
||||
let vl = v.len();
|
||||
Ok(Self {
|
||||
bytes: v.try_into().map_err(|_| {
|
||||
VeilidAPIError::generic(format!(
|
||||
"Expected a Vec of length {} but it was {}",
|
||||
$size, vl
|
||||
))
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bit(&self, index: usize) -> bool {
|
||||
assert!(index < ($size * 8));
|
||||
let bi = index / 8;
|
||||
let ti = 7 - (index % 8);
|
||||
((self.bytes[bi] >> ti) & 1) != 0
|
||||
}
|
||||
|
||||
pub fn first_nonzero_bit(&self) -> Option<usize> {
|
||||
for i in 0..$size {
|
||||
let b = self.bytes[i];
|
||||
if b != 0 {
|
||||
for n in 0..8 {
|
||||
if ((b >> (7 - n)) & 1u8) != 0u8 {
|
||||
return Some((i * 8) + n);
|
||||
}
|
||||
}
|
||||
panic!("wtf")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn nibble(&self, index: usize) -> u8 {
|
||||
assert!(index < ($size * 2));
|
||||
let bi = index / 2;
|
||||
if index & 1 == 0 {
|
||||
(self.bytes[bi] >> 4) & 0xFu8
|
||||
} else {
|
||||
self.bytes[bi] & 0xFu8
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_nonzero_nibble(&self) -> Option<(usize, u8)> {
|
||||
for i in 0..($size * 2) {
|
||||
let n = self.nibble(i);
|
||||
if n != 0 {
|
||||
return Some((i, n));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> String {
|
||||
BASE64URL_NOPAD.encode(&self.bytes)
|
||||
}
|
||||
|
||||
pub fn try_decode<S: AsRef<str>>(input: S) -> Result<Self, VeilidAPIError> {
|
||||
let mut bytes = [0u8; $size];
|
||||
|
||||
let res = BASE64URL_NOPAD.decode_len(input.as_ref().len());
|
||||
match res {
|
||||
Ok(v) => {
|
||||
if v != $size {
|
||||
apibail_generic!("Incorrect length in decode");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
apibail_generic!("Failed to decode");
|
||||
}
|
||||
}
|
||||
|
||||
let res = BASE64URL_NOPAD.decode_mut(input.as_ref().as_bytes(), &mut bytes);
|
||||
match res {
|
||||
Ok(_) => Ok(Self::new(bytes)),
|
||||
Err(_) => apibail_generic!("Failed to decode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
//write!(f, "{}", String::from(self))
|
||||
write!(f, "{}", self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, concat!(stringify!($name), "("))?;
|
||||
write!(f, "{}", self.encode())?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&$name> for String {
|
||||
fn from(value: &$name) -> Self {
|
||||
// let mut s = String::new();
|
||||
// for n in 0..($size / 8) {
|
||||
// let b: [u8; 8] = value.bytes[n * 8..(n + 1) * 8].try_into().unwrap();
|
||||
// s.push_str(hex::encode(b).as_str());
|
||||
// }
|
||||
// s
|
||||
value.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for $name {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
$name::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for $name {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
// let mut out = $name::default();
|
||||
// if value == "" {
|
||||
// return Ok(out);
|
||||
// }
|
||||
// if value.len() != ($size * 2) {
|
||||
// apibail_generic!(concat!(stringify!($name), " is incorrect length"));
|
||||
// }
|
||||
// match hex::decode_to_slice(value, &mut out.bytes) {
|
||||
// Ok(_) => Ok(out),
|
||||
// Err(err) => Err(VeilidAPIError::generic(err)),
|
||||
// }
|
||||
Self::try_decode(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
byte_array_type!(DHTKey, DHT_KEY_LENGTH);
|
||||
byte_array_type!(DHTKeySecret, DHT_KEY_SECRET_LENGTH);
|
||||
byte_array_type!(DHTSignature, DHT_SIGNATURE_LENGTH);
|
||||
byte_array_type!(DHTKeyDistance, DHT_KEY_LENGTH);
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
||||
struct Blake3Digest512 {
|
||||
dig: blake3::Hasher,
|
||||
}
|
||||
|
||||
impl Digest for Blake3Digest512 {
|
||||
type OutputSize = U64;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
dig: blake3::Hasher::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||
self.dig.update(data.as_ref());
|
||||
}
|
||||
|
||||
fn chain(mut self, data: impl AsRef<[u8]>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.update(data);
|
||||
self
|
||||
}
|
||||
|
||||
fn finalize(self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn finalize_reset(&mut self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
self.reset();
|
||||
out
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.dig.reset();
|
||||
}
|
||||
|
||||
fn output_size() -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn digest(data: &[u8]) -> Output<Self> {
|
||||
let mut dig = blake3::Hasher::new();
|
||||
dig.update(data);
|
||||
let mut b = [0u8; 64];
|
||||
dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
||||
pub fn generate_secret() -> (DHTKey, DHTKeySecret) {
|
||||
let mut csprng = VeilidRng {};
|
||||
let keypair = Keypair::generate(&mut csprng);
|
||||
let dht_key = DHTKey::new(keypair.public.to_bytes());
|
||||
let dht_key_secret = DHTKeySecret::new(keypair.secret.to_bytes());
|
||||
|
||||
(dht_key, dht_key_secret)
|
||||
}
|
||||
|
||||
pub fn sign(
|
||||
dht_key: &DHTKey,
|
||||
dht_key_secret: &DHTKeySecret,
|
||||
data: &[u8],
|
||||
) -> Result<DHTSignature, VeilidAPIError> {
|
||||
let mut kpb: [u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH] =
|
||||
[0u8; DHT_KEY_SECRET_LENGTH + DHT_KEY_LENGTH];
|
||||
|
||||
kpb[..DHT_KEY_SECRET_LENGTH].copy_from_slice(&dht_key_secret.bytes);
|
||||
kpb[DHT_KEY_SECRET_LENGTH..].copy_from_slice(&dht_key.bytes);
|
||||
let keypair = Keypair::from_bytes(&kpb)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Keypair is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
let sig = keypair
|
||||
.sign_prehashed(dig, None)
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
|
||||
let dht_sig = DHTSignature::new(sig.to_bytes());
|
||||
Ok(dht_sig)
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
dht_key: &DHTKey,
|
||||
data: &[u8],
|
||||
signature: &DHTSignature,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let pk = PublicKey::from_bytes(&dht_key.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
|
||||
let sig = Signature::from_bytes(&signature.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Signature is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
pk.verify_prehashed(dig, None, &sig)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_hash(data: &[u8]) -> DHTKey {
|
||||
DHTKey::new(*blake3::hash(data).as_bytes())
|
||||
}
|
||||
|
||||
pub fn validate_hash(data: &[u8], dht_key: &DHTKey) -> bool {
|
||||
let bytes = *blake3::hash(data).as_bytes();
|
||||
|
||||
bytes == dht_key.bytes
|
||||
}
|
||||
|
||||
pub fn validate_key(dht_key: &DHTKey, dht_key_secret: &DHTKeySecret) -> bool {
|
||||
let data = vec![0u8; 512];
|
||||
let sig = match sign(dht_key, dht_key_secret, &data) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
verify(dht_key, &data, &sig).is_ok()
|
||||
}
|
||||
|
||||
pub fn distance(key1: &DHTKey, key2: &DHTKey) -> DHTKeyDistance {
|
||||
let mut bytes = [0u8; DHT_KEY_LENGTH];
|
||||
|
||||
for (n, byte) in bytes.iter_mut().enumerate() {
|
||||
*byte = key1.bytes[n] ^ key2.bytes[n];
|
||||
}
|
||||
|
||||
DHTKeyDistance::new(bytes)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn sort_closest_fn(key: DHTKey) -> impl FnMut(&DHTKey, &DHTKey) -> std::cmp::Ordering {
|
||||
move |k1, k2| distance(k1, &key).cmp(&distance(k2, &key))
|
||||
}
|
@ -1,126 +1,141 @@
|
||||
mod byte_array_types;
|
||||
mod dh_cache;
|
||||
mod envelope;
|
||||
mod key;
|
||||
mod receipt;
|
||||
mod types;
|
||||
mod value;
|
||||
|
||||
pub mod crypto_system;
|
||||
pub mod tests;
|
||||
pub mod vld0;
|
||||
|
||||
pub use byte_array_types::*;
|
||||
pub use crypto_system::*;
|
||||
pub use dh_cache::*;
|
||||
pub use envelope::*;
|
||||
pub use key::*;
|
||||
pub use receipt::*;
|
||||
pub use types::*;
|
||||
pub use value::*;
|
||||
|
||||
pub const MIN_CRYPTO_VERSION: u8 = 0u8;
|
||||
pub const MAX_CRYPTO_VERSION: u8 = 0u8;
|
||||
pub use vld0::*;
|
||||
|
||||
use crate::*;
|
||||
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
||||
use chacha20::XChaCha20;
|
||||
use chacha20poly1305 as ch;
|
||||
use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
||||
use core::convert::TryInto;
|
||||
use curve25519_dalek as cd;
|
||||
use ed25519_dalek as ed;
|
||||
use hashlink::linked_hash_map::Entry;
|
||||
use hashlink::LruCache;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use x25519_dalek as xd;
|
||||
// Handle to a particular cryptosystem
|
||||
pub type CryptoSystemVersion = Arc<dyn CryptoSystem + Send + Sync>;
|
||||
|
||||
pub type SharedSecret = [u8; 32];
|
||||
pub type Nonce = [u8; 24];
|
||||
|
||||
const DH_CACHE_SIZE: usize = 1024;
|
||||
pub const AEAD_OVERHEAD: usize = 16;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
struct DHCacheKey {
|
||||
key: DHTKey,
|
||||
secret: DHTKeySecret,
|
||||
/// Crypto kinds in order of preference, best cryptosystem is the first one, worst is the last one
|
||||
pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_VLD0];
|
||||
/// Number of cryptosystem signatures to keep on structures if many are present beyond the ones we consider valid
|
||||
pub const MAX_CRYPTO_KINDS: usize = 3;
|
||||
/// Return the best cryptosystem kind we support
|
||||
pub fn best_crypto_kind() -> CryptoKind {
|
||||
VALID_CRYPTO_KINDS[0]
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DHCacheValue {
|
||||
shared_secret: SharedSecret,
|
||||
}
|
||||
type DHCache = LruCache<DHCacheKey, DHCacheValue>;
|
||||
// Version number of envelope format
|
||||
pub type EnvelopeVersion = u8;
|
||||
|
||||
fn cache_to_bytes(cache: &DHCache) -> Vec<u8> {
|
||||
let cnt: usize = cache.len();
|
||||
let mut out: Vec<u8> = Vec::with_capacity(cnt * (32 + 32 + 32));
|
||||
for e in cache.iter() {
|
||||
out.extend(&e.0.key.bytes);
|
||||
out.extend(&e.0.secret.bytes);
|
||||
out.extend(&e.1.shared_secret);
|
||||
}
|
||||
let mut rev: Vec<u8> = Vec::with_capacity(out.len());
|
||||
for d in out.chunks(32 + 32 + 32).rev() {
|
||||
rev.extend(d);
|
||||
}
|
||||
rev
|
||||
}
|
||||
|
||||
fn bytes_to_cache(bytes: &[u8], cache: &mut DHCache) {
|
||||
for d in bytes.chunks(32 + 32 + 32) {
|
||||
let k = DHCacheKey {
|
||||
key: DHTKey::new(d[0..32].try_into().expect("asdf")),
|
||||
secret: DHTKeySecret::new(d[32..64].try_into().expect("asdf")),
|
||||
};
|
||||
let v = DHCacheValue {
|
||||
shared_secret: d[64..96].try_into().expect("asdf"),
|
||||
};
|
||||
cache.insert(k, v);
|
||||
}
|
||||
/// Envelope versions in order of preference, best envelope version is the first one, worst is the last one
|
||||
pub const VALID_ENVELOPE_VERSIONS: [EnvelopeVersion; 1] = [0u8];
|
||||
/// Number of envelope versions to keep on structures if many are present beyond the ones we consider valid
|
||||
pub const MAX_ENVELOPE_VERSIONS: usize = 3;
|
||||
/// Return the best envelope version we support
|
||||
pub fn best_envelope_version() -> EnvelopeVersion {
|
||||
VALID_ENVELOPE_VERSIONS[0]
|
||||
}
|
||||
|
||||
struct CryptoInner {
|
||||
table_store: TableStore,
|
||||
node_id: DHTKey,
|
||||
node_id_secret: DHTKeySecret,
|
||||
dh_cache: DHCache,
|
||||
flush_future: Option<SendPinBoxFuture<()>>,
|
||||
crypto_vld0: Option<Arc<dyn CryptoSystem + Send + Sync>>,
|
||||
}
|
||||
|
||||
struct CryptoUnlockedInner {
|
||||
config: VeilidConfig,
|
||||
table_store: TableStore,
|
||||
protected_store: ProtectedStore,
|
||||
}
|
||||
|
||||
/// Crypto factory implementation
|
||||
#[derive(Clone)]
|
||||
pub struct Crypto {
|
||||
config: VeilidConfig,
|
||||
unlocked_inner: Arc<CryptoUnlockedInner>,
|
||||
inner: Arc<Mutex<CryptoInner>>,
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
fn new_inner(table_store: TableStore) -> CryptoInner {
|
||||
fn new_inner() -> CryptoInner {
|
||||
CryptoInner {
|
||||
table_store,
|
||||
node_id: Default::default(),
|
||||
node_id_secret: Default::default(),
|
||||
dh_cache: DHCache::new(DH_CACHE_SIZE),
|
||||
flush_future: None,
|
||||
crypto_vld0: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(config: VeilidConfig, table_store: TableStore) -> Self {
|
||||
Self {
|
||||
config,
|
||||
inner: Arc::new(Mutex::new(Self::new_inner(table_store))),
|
||||
}
|
||||
pub fn new(
|
||||
config: VeilidConfig,
|
||||
table_store: TableStore,
|
||||
protected_store: ProtectedStore,
|
||||
) -> Self {
|
||||
let out = Self {
|
||||
unlocked_inner: Arc::new(CryptoUnlockedInner {
|
||||
config,
|
||||
table_store,
|
||||
protected_store,
|
||||
}),
|
||||
inner: Arc::new(Mutex::new(Self::new_inner())),
|
||||
};
|
||||
|
||||
out.inner.lock().crypto_vld0 = Some(Arc::new(vld0::CryptoSystemVLD0::new(out.clone())));
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn config(&self) -> VeilidConfig {
|
||||
self.unlocked_inner.config.clone()
|
||||
}
|
||||
|
||||
pub async fn init(&self) -> EyreResult<()> {
|
||||
trace!("Crypto::init");
|
||||
let table_store = self.unlocked_inner.table_store.clone();
|
||||
|
||||
// Init node id from config
|
||||
if let Err(e) = self
|
||||
.unlocked_inner
|
||||
.config
|
||||
.init_node_ids(self.clone(), self.unlocked_inner.protected_store.clone())
|
||||
.await
|
||||
{
|
||||
return Err(e).wrap_err("init node id failed");
|
||||
}
|
||||
|
||||
// make local copy of node id for easy access
|
||||
let (table_store, node_id) = {
|
||||
let mut inner = self.inner.lock();
|
||||
let c = self.config.get();
|
||||
inner.node_id = c.network.node_id.unwrap();
|
||||
inner.node_id_secret = c.network.node_id_secret.unwrap();
|
||||
(inner.table_store.clone(), c.network.node_id)
|
||||
let mut cache_validity_key: Vec<u8> = Vec::new();
|
||||
{
|
||||
let c = self.unlocked_inner.config.get();
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
cache_validity_key.append(
|
||||
&mut c
|
||||
.network
|
||||
.routing_table
|
||||
.node_id
|
||||
.get(ck)
|
||||
.unwrap()
|
||||
.value
|
||||
.bytes
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// load caches if they are valid for this node id
|
||||
let mut db = table_store.open("crypto_caches", 1).await?;
|
||||
let caches_valid = match db.load(0, b"node_id")? {
|
||||
Some(v) => v.as_slice() == node_id.unwrap().bytes,
|
||||
let caches_valid = match db.load(0, b"cache_validity_key")? {
|
||||
Some(v) => v == cache_validity_key,
|
||||
None => false,
|
||||
};
|
||||
if caches_valid {
|
||||
@ -132,7 +147,8 @@ impl Crypto {
|
||||
drop(db);
|
||||
table_store.delete("crypto_caches").await?;
|
||||
db = table_store.open("crypto_caches", 1).await?;
|
||||
db.store(0, b"node_id", &node_id.unwrap().bytes).await?;
|
||||
db.store(0, b"cache_validity_key", &cache_validity_key)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Schedule flushing
|
||||
@ -152,13 +168,16 @@ impl Crypto {
|
||||
|
||||
pub async fn flush(&self) -> EyreResult<()> {
|
||||
//trace!("Crypto::flush");
|
||||
let (table_store, cache_bytes) = {
|
||||
let cache_bytes = {
|
||||
let inner = self.inner.lock();
|
||||
let cache_bytes = cache_to_bytes(&inner.dh_cache);
|
||||
(inner.table_store.clone(), cache_bytes)
|
||||
cache_to_bytes(&inner.dh_cache)
|
||||
};
|
||||
|
||||
let db = table_store.open("crypto_caches", 1).await?;
|
||||
let db = self
|
||||
.unlocked_inner
|
||||
.table_store
|
||||
.open("crypto_caches", 1)
|
||||
.await?;
|
||||
db.store(0, b"dh_cache", &cache_bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
@ -180,139 +199,99 @@ impl Crypto {
|
||||
};
|
||||
}
|
||||
|
||||
fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, VeilidAPIError> {
|
||||
let bytes = key.to_bytes();
|
||||
let compressed = cd::edwards::CompressedEdwardsY(bytes);
|
||||
let point = compressed
|
||||
.decompress()
|
||||
.ok_or_else(|| VeilidAPIError::internal("ed25519_to_x25519_pk failed"))?;
|
||||
let mp = point.to_montgomery();
|
||||
Ok(xd::PublicKey::from(mp.to_bytes()))
|
||||
}
|
||||
fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, VeilidAPIError> {
|
||||
let exp = ed::ExpandedSecretKey::from(key);
|
||||
let bytes: [u8; ed::EXPANDED_SECRET_KEY_LENGTH] = exp.to_bytes();
|
||||
let lowbytes: [u8; 32] = bytes[0..32].try_into().map_err(VeilidAPIError::internal)?;
|
||||
Ok(xd::StaticSecret::from(lowbytes))
|
||||
/// Factory method to get a specific crypto version
|
||||
pub fn get(&self, kind: CryptoKind) -> Option<CryptoSystemVersion> {
|
||||
let inner = self.inner.lock();
|
||||
match kind {
|
||||
CRYPTO_KIND_VLD0 => Some(inner.crypto_vld0.clone().unwrap()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cached_dh(
|
||||
// Factory method to get the best crypto version
|
||||
pub fn best(&self) -> CryptoSystemVersion {
|
||||
self.get(best_crypto_kind()).unwrap()
|
||||
}
|
||||
|
||||
/// Signature set verification
|
||||
/// Returns the set of signature cryptokinds that validate and are supported
|
||||
/// If any cryptokinds are supported and do not validate, the whole operation
|
||||
/// returns an error
|
||||
pub fn verify_signatures(
|
||||
&self,
|
||||
key: &DHTKey,
|
||||
secret: &DHTKeySecret,
|
||||
node_ids: &[TypedKey],
|
||||
data: &[u8],
|
||||
typed_signatures: &[TypedSignature],
|
||||
) -> Result<TypedKeySet, VeilidAPIError> {
|
||||
let mut out = TypedKeySet::with_capacity(node_ids.len());
|
||||
for sig in typed_signatures {
|
||||
for nid in node_ids {
|
||||
if nid.kind == sig.kind {
|
||||
if let Some(vcrypto) = self.get(sig.kind) {
|
||||
vcrypto.verify(&nid.value, data, &sig.value)?;
|
||||
out.add(*nid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Signature set generation
|
||||
/// Generates the set of signatures that are supported
|
||||
/// Any cryptokinds that are not supported are silently dropped
|
||||
pub fn generate_signatures<F, R>(
|
||||
&self,
|
||||
data: &[u8],
|
||||
typed_key_pairs: &[TypedKeyPair],
|
||||
transform: F,
|
||||
) -> Result<Vec<R>, VeilidAPIError>
|
||||
where
|
||||
F: Fn(&TypedKeyPair, Signature) -> R,
|
||||
{
|
||||
let mut out = Vec::<R>::with_capacity(typed_key_pairs.len());
|
||||
for kp in typed_key_pairs {
|
||||
if let Some(vcrypto) = self.get(kp.kind) {
|
||||
let sig = vcrypto.sign(&kp.value.key, &kp.value.secret, data)?;
|
||||
out.push(transform(kp, sig))
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Generate keypair
|
||||
/// Does not require startup/init
|
||||
pub fn generate_keypair(crypto_kind: CryptoKind) -> Result<TypedKeyPair, VeilidAPIError> {
|
||||
if crypto_kind == CRYPTO_KIND_VLD0 {
|
||||
let kp = vld0_generate_keypair();
|
||||
return Ok(TypedKeyPair::new(crypto_kind, kp));
|
||||
}
|
||||
Err(VeilidAPIError::generic("invalid crypto kind"))
|
||||
}
|
||||
|
||||
// Internal utilities
|
||||
|
||||
fn cached_dh_internal<T: CryptoSystem>(
|
||||
&self,
|
||||
vcrypto: &T,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError> {
|
||||
Ok(
|
||||
match self.inner.lock().dh_cache.entry(DHCacheKey {
|
||||
key: *key,
|
||||
secret: *secret,
|
||||
}) {
|
||||
match self.inner.lock().dh_cache.entry(
|
||||
DHCacheKey {
|
||||
key: *key,
|
||||
secret: *secret,
|
||||
},
|
||||
|_k, _v| {},
|
||||
) {
|
||||
Entry::Occupied(e) => e.get().shared_secret,
|
||||
Entry::Vacant(e) => {
|
||||
let shared_secret = Self::compute_dh(key, secret)?;
|
||||
let shared_secret = vcrypto.compute_dh(key, secret)?;
|
||||
e.insert(DHCacheValue { shared_secret });
|
||||
shared_secret
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
///////////
|
||||
// These are safe to use regardless of initialization status
|
||||
|
||||
pub fn compute_dh(key: &DHTKey, secret: &DHTKeySecret) -> Result<SharedSecret, VeilidAPIError> {
|
||||
let pk_ed = ed::PublicKey::from_bytes(&key.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let pk_xd = Self::ed25519_to_x25519_pk(&pk_ed)?;
|
||||
let sk_ed = ed::SecretKey::from_bytes(&secret.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let sk_xd = Self::ed25519_to_x25519_sk(&sk_ed)?;
|
||||
Ok(sk_xd.diffie_hellman(&pk_xd).to_bytes())
|
||||
}
|
||||
|
||||
pub fn get_random_nonce() -> Nonce {
|
||||
let mut nonce = [0u8; 24];
|
||||
random_bytes(&mut nonce).unwrap();
|
||||
nonce
|
||||
}
|
||||
|
||||
pub fn get_random_secret() -> SharedSecret {
|
||||
let mut s = [0u8; 32];
|
||||
random_bytes(&mut s).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
pub fn decrypt_in_place_aead(
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(*shared_secret);
|
||||
let xnonce = ch::XNonce::from(*nonce);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
pub fn decrypt_aead(
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
Self::decrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn encrypt_in_place_aead(
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(*shared_secret);
|
||||
let xnonce = ch::XNonce::from(*nonce);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
|
||||
aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
pub fn encrypt_aead(
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
Self::encrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn crypt_in_place_no_auth(body: &mut Vec<u8>, nonce: &Nonce, shared_secret: &SharedSecret) {
|
||||
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
|
||||
cipher.apply_keystream(body);
|
||||
}
|
||||
|
||||
pub fn crypt_b2b_no_auth(
|
||||
in_buf: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8> {
|
||||
let mut cipher = XChaCha20::new(shared_secret.into(), nonce.into());
|
||||
// Allocate uninitialized memory, aligned to 8 byte boundary because capnp is faster this way
|
||||
// and the Vec returned here will be used to hold decrypted rpc messages
|
||||
let mut out_buf = unsafe { aligned_8_u8_vec_uninit(in_buf.len()) };
|
||||
cipher.apply_keystream_b2b(in_buf, &mut out_buf).unwrap();
|
||||
out_buf
|
||||
}
|
||||
|
||||
pub fn crypt_no_auth(body: &[u8], nonce: &Nonce, shared_secret: &SharedSecret) -> Vec<u8> {
|
||||
Self::crypt_b2b_no_auth(body, nonce, shared_secret)
|
||||
}
|
||||
}
|
||||
|
70
veilid-core/src/crypto/none/blake3digest512.rs
Normal file
70
veilid-core/src/crypto/none/blake3digest512.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use digest::generic_array::typenum::U64;
|
||||
use digest::{Digest, Output};
|
||||
use generic_array::GenericArray;
|
||||
|
||||
pub struct Blake3Digest512 {
|
||||
dig: blake3::Hasher,
|
||||
}
|
||||
|
||||
impl Digest for Blake3Digest512 {
|
||||
type OutputSize = U64;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
dig: blake3::Hasher::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||
self.dig.update(data.as_ref());
|
||||
}
|
||||
|
||||
fn chain(mut self, data: impl AsRef<[u8]>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.update(data);
|
||||
self
|
||||
}
|
||||
|
||||
fn finalize(self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn finalize_reset(&mut self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
self.reset();
|
||||
out
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.dig.reset();
|
||||
}
|
||||
|
||||
fn output_size() -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn digest(data: &[u8]) -> Output<Self> {
|
||||
let mut dig = blake3::Hasher::new();
|
||||
dig.update(data);
|
||||
let mut b = [0u8; 64];
|
||||
dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
300
veilid-core/src/crypto/none/mod.rs
Normal file
300
veilid-core/src/crypto/none/mod.rs
Normal file
@ -0,0 +1,300 @@
|
||||
pub mod blake3digest512;
|
||||
pub use blake3digest512::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
||||
use chacha20::XChaCha20;
|
||||
use chacha20poly1305 as ch;
|
||||
use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
||||
use core::convert::TryInto;
|
||||
use curve25519_dalek as cd;
|
||||
use digest::Digest;
|
||||
use ed25519_dalek as ed;
|
||||
use x25519_dalek as xd;
|
||||
|
||||
const AEAD_OVERHEAD: usize = 16;
|
||||
pub const CRYPTO_KIND_VLD0: CryptoKind = FourCC([b'V', b'L', b'D', b'0']);
|
||||
|
||||
fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, VeilidAPIError> {
|
||||
let bytes = key.to_bytes();
|
||||
let compressed = cd::edwards::CompressedEdwardsY(bytes);
|
||||
let point = compressed
|
||||
.decompress()
|
||||
.ok_or_else(|| VeilidAPIError::internal("ed25519_to_x25519_pk failed"))?;
|
||||
let mp = point.to_montgomery();
|
||||
Ok(xd::PublicKey::from(mp.to_bytes()))
|
||||
}
|
||||
fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, VeilidAPIError> {
|
||||
let exp = ed::ExpandedSecretKey::from(key);
|
||||
let bytes: [u8; ed::EXPANDED_SECRET_KEY_LENGTH] = exp.to_bytes();
|
||||
let lowbytes: [u8; 32] = bytes[0..32].try_into().map_err(VeilidAPIError::internal)?;
|
||||
Ok(xd::StaticSecret::from(lowbytes))
|
||||
}
|
||||
|
||||
pub fn vld0_generate_keypair() -> KeyPair {
|
||||
let mut csprng = VeilidRng {};
|
||||
let keypair = ed::Keypair::generate(&mut csprng);
|
||||
let dht_key = PublicKey::new(keypair.public.to_bytes());
|
||||
let dht_key_secret = SecretKey::new(keypair.secret.to_bytes());
|
||||
|
||||
KeyPair::new(dht_key, dht_key_secret)
|
||||
}
|
||||
|
||||
/// V0 CryptoSystem
|
||||
#[derive(Clone)]
|
||||
pub struct CryptoSystemVLD0 {
|
||||
crypto: Crypto,
|
||||
}
|
||||
|
||||
impl CryptoSystemVLD0 {
|
||||
pub fn new(crypto: Crypto) -> Self {
|
||||
Self { crypto }
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptoSystem for CryptoSystemVLD0 {
|
||||
// Accessors
|
||||
fn kind(&self) -> CryptoKind {
|
||||
CRYPTO_KIND_VLD0
|
||||
}
|
||||
|
||||
fn crypto(&self) -> Crypto {
|
||||
self.crypto.clone()
|
||||
}
|
||||
|
||||
// Cached Operations
|
||||
fn cached_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError> {
|
||||
self.crypto
|
||||
.cached_dh_internal::<CryptoSystemVLD0>(self, key, secret)
|
||||
}
|
||||
|
||||
// Generation
|
||||
fn random_nonce(&self) -> Nonce {
|
||||
let mut nonce = [0u8; 24];
|
||||
random_bytes(&mut nonce).unwrap();
|
||||
Nonce::new(nonce)
|
||||
}
|
||||
fn random_shared_secret(&self) -> SharedSecret {
|
||||
let mut s = [0u8; 32];
|
||||
random_bytes(&mut s).unwrap();
|
||||
SharedSecret::new(s)
|
||||
}
|
||||
fn compute_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError> {
|
||||
let pk_ed = ed::PublicKey::from_bytes(&key.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let pk_xd = ed25519_to_x25519_pk(&pk_ed)?;
|
||||
let sk_ed = ed::SecretKey::from_bytes(&secret.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let sk_xd = ed25519_to_x25519_sk(&sk_ed)?;
|
||||
Ok(SharedSecret::new(sk_xd.diffie_hellman(&pk_xd).to_bytes()))
|
||||
}
|
||||
fn generate_keypair(&self) -> KeyPair {
|
||||
vld0_generate_keypair()
|
||||
}
|
||||
fn generate_hash(&self, data: &[u8]) -> PublicKey {
|
||||
PublicKey::new(*blake3::hash(data).as_bytes())
|
||||
}
|
||||
fn generate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
) -> Result<PublicKey, VeilidAPIError> {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
|
||||
Ok(PublicKey::new(*hasher.finalize().as_bytes()))
|
||||
}
|
||||
|
||||
// Validation
|
||||
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool {
|
||||
let data = vec![0u8; 512];
|
||||
let sig = match self.sign(dht_key, dht_key_secret, &data) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
self.verify(dht_key, &data, &sig).is_ok()
|
||||
}
|
||||
fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool {
|
||||
let bytes = *blake3::hash(data).as_bytes();
|
||||
|
||||
bytes == dht_key.bytes
|
||||
}
|
||||
fn validate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
dht_key: &PublicKey,
|
||||
) -> Result<bool, VeilidAPIError> {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
|
||||
let bytes = *hasher.finalize().as_bytes();
|
||||
Ok(bytes == dht_key.bytes)
|
||||
}
|
||||
// Distance Metric
|
||||
fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> PublicKeyDistance {
|
||||
let mut bytes = [0u8; PUBLIC_KEY_LENGTH];
|
||||
|
||||
for (n, byte) in bytes.iter_mut().enumerate() {
|
||||
*byte = key1.bytes[n] ^ key2.bytes[n];
|
||||
}
|
||||
|
||||
PublicKeyDistance::new(bytes)
|
||||
}
|
||||
|
||||
// Authentication
|
||||
fn sign(
|
||||
&self,
|
||||
dht_key: &PublicKey,
|
||||
dht_key_secret: &SecretKey,
|
||||
data: &[u8],
|
||||
) -> Result<Signature, VeilidAPIError> {
|
||||
let mut kpb: [u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] =
|
||||
[0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH];
|
||||
|
||||
kpb[..SECRET_KEY_LENGTH].copy_from_slice(&dht_key_secret.bytes);
|
||||
kpb[SECRET_KEY_LENGTH..].copy_from_slice(&dht_key.bytes);
|
||||
let keypair = ed::Keypair::from_bytes(&kpb)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Keypair is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
let sig = keypair
|
||||
.sign_prehashed(dig, None)
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
|
||||
let dht_sig = Signature::new(sig.to_bytes());
|
||||
Ok(dht_sig)
|
||||
}
|
||||
fn verify(
|
||||
&self,
|
||||
dht_key: &PublicKey,
|
||||
data: &[u8],
|
||||
signature: &Signature,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let pk = ed::PublicKey::from_bytes(&dht_key.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
|
||||
let sig = ed::Signature::from_bytes(&signature.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Signature is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
pk.verify_prehashed(dig, None, &sig)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// AEAD Encrypt/Decrypt
|
||||
fn aead_overhead(&self) -> usize {
|
||||
AEAD_OVERHEAD
|
||||
}
|
||||
fn decrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(shared_secret.bytes);
|
||||
let xnonce = ch::XNonce::from(nonce.bytes);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
fn decrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
self.decrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn encrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(shared_secret.bytes);
|
||||
let xnonce = ch::XNonce::from(nonce.bytes);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
|
||||
aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
fn encrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
self.encrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
// NoAuth Encrypt/Decrypt
|
||||
fn crypt_in_place_no_auth(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) {
|
||||
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
|
||||
cipher.apply_keystream(body);
|
||||
}
|
||||
|
||||
fn crypt_b2b_no_auth(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
out_buf: &mut [u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) {
|
||||
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
|
||||
cipher.apply_keystream_b2b(in_buf, out_buf).unwrap();
|
||||
}
|
||||
|
||||
fn crypt_no_auth_aligned_8(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8> {
|
||||
let mut out_buf = unsafe { aligned_8_u8_vec_uninit(in_buf.len()) };
|
||||
self.crypt_b2b_no_auth(in_buf, &mut out_buf, nonce, shared_secret);
|
||||
out_buf
|
||||
}
|
||||
|
||||
fn crypt_no_auth_unaligned(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8> {
|
||||
let mut out_buf = unsafe { unaligned_u8_vec_uninit(in_buf.len()) };
|
||||
self.crypt_b2b_no_auth(in_buf, &mut out_buf, nonce, shared_secret);
|
||||
out_buf
|
||||
}
|
||||
}
|
@ -3,60 +3,56 @@
|
||||
use super::*;
|
||||
use crate::*;
|
||||
use core::convert::TryInto;
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
|
||||
// #[repr(C, packed)]
|
||||
// struct ReceiptHeader {
|
||||
// // Size is at least 8 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
|
||||
// magic: [u8; 4], // 0x00: 0x52 0x43 0x50 0x54 ("RCPT")
|
||||
// version: u8, // 0x04: 0 = ReceiptV0
|
||||
// reserved: u8, // 0x05: Reserved for future use
|
||||
// }
|
||||
/// Out-of-band receipts are versioned along with envelope versions
|
||||
///
|
||||
/// These are the formats for the on-the-wire serialization performed by this module
|
||||
///
|
||||
/// #[repr(C, packed)]
|
||||
/// struct ReceiptHeader {
|
||||
/// // Size is at least 4 bytes. Depending on the version specified, the size may vary and should be case to the appropriate struct
|
||||
/// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP")
|
||||
/// version: u8, // 0x03: 0 = ReceiptV0
|
||||
/// }
|
||||
///
|
||||
/// #[repr(C, packed)]
|
||||
/// struct ReceiptV0 {
|
||||
/// // Size is 66 bytes without extra data and signature, 130 with signature
|
||||
/// magic: [u8; 3], // 0x00: 0x52 0x43 0x50 ("RCP")
|
||||
/// version: u8, // 0x03: 0 = ReceiptV0
|
||||
/// crypto_kind: [u8; 4], // 0x04: CryptoSystemVersion FOURCC code
|
||||
/// size: u16, // 0x08: Total size of the receipt including the extra data and the signature. Maximum size is 1380 bytes.
|
||||
/// nonce: [u8; 24], // 0x0A: Randomly chosen bytes that represent a unique receipt. Could be used to encrypt the extra data, but it's not required.
|
||||
/// sender_id: [u8; 32], // 0x22: Node ID of the message source, which is the public key of the sender
|
||||
/// extra_data: [u8; ??], // 0x42: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1250 bytes)
|
||||
/// signature: [u8; 64], // 0x?? (end-0x40): Signature of the entire receipt including header and extra data is appended to the packet
|
||||
/// }
|
||||
|
||||
// #[repr(C, packed)]
|
||||
// struct ReceiptV0 {
|
||||
// // Size is 106 bytes.
|
||||
// magic: [u8; 4], // 0x00: 0x52 0x43 0x50 0x54 ("RCPT")
|
||||
// version: u8, // 0x04: 0 = ReceiptV0
|
||||
// reserved: u8, // 0x05: Reserved for future use
|
||||
// size: u16, // 0x06: Total size of the receipt including the extra data and the signature. Maximum size is 1152 bytes.
|
||||
// nonce: [u8; 24], // 0x08: Randomly chosen bytes that represent a unique receipt. Could be used to encrypt the extra data, but it's not required.
|
||||
// sender_id: [u8; 32], // 0x20: Node ID of the message source, which is the Ed25519 public key of the sender
|
||||
// extra_data: [u8; ??], // 0x40: Extra data is appended (arbitrary extra data, not encrypted by receipt itself, maximum size is 1024 bytes)
|
||||
// signature: [u8; 64], // 0x?? (end-0x40): Ed25519 signature of the entire receipt including header and extra data is appended to the packet
|
||||
// }
|
||||
|
||||
pub const MAX_RECEIPT_SIZE: usize = 1152;
|
||||
pub const MAX_EXTRA_DATA_SIZE: usize = 1024;
|
||||
pub const MIN_RECEIPT_SIZE: usize = 128;
|
||||
pub const RECEIPT_MAGIC: &[u8; 4] = b"RCPT";
|
||||
pub type ReceiptNonce = [u8; 24];
|
||||
|
||||
pub trait Encodable {
|
||||
fn encode(&self) -> String;
|
||||
}
|
||||
|
||||
impl Encodable for ReceiptNonce {
|
||||
fn encode(&self) -> String {
|
||||
BASE64URL_NOPAD.encode(self)
|
||||
}
|
||||
}
|
||||
pub const MAX_RECEIPT_SIZE: usize = 1380;
|
||||
pub const MAX_EXTRA_DATA_SIZE: usize = MAX_RECEIPT_SIZE - MIN_RECEIPT_SIZE; // 1250
|
||||
pub const MIN_RECEIPT_SIZE: usize = 130;
|
||||
pub const RECEIPT_MAGIC: &[u8; 3] = b"RCP";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Receipt {
|
||||
version: u8,
|
||||
nonce: ReceiptNonce,
|
||||
sender_id: DHTKey,
|
||||
crypto_kind: CryptoKind,
|
||||
nonce: Nonce,
|
||||
sender_id: PublicKey,
|
||||
extra_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Receipt {
|
||||
pub fn try_new<D: AsRef<[u8]>>(
|
||||
version: u8,
|
||||
nonce: ReceiptNonce,
|
||||
sender_id: DHTKey,
|
||||
crypto_kind: CryptoKind,
|
||||
nonce: Nonce,
|
||||
sender_id: PublicKey,
|
||||
extra_data: D,
|
||||
) -> Result<Self, VeilidAPIError> {
|
||||
assert!(VALID_ENVELOPE_VERSIONS.contains(&version));
|
||||
assert!(VALID_CRYPTO_KINDS.contains(&crypto_kind));
|
||||
|
||||
if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE {
|
||||
apibail_parse_error!(
|
||||
"extra data too large for receipt",
|
||||
@ -65,20 +61,21 @@ impl Receipt {
|
||||
}
|
||||
Ok(Self {
|
||||
version,
|
||||
crypto_kind,
|
||||
nonce,
|
||||
sender_id,
|
||||
extra_data: Vec::from(extra_data.as_ref()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_signed_data(data: &[u8]) -> Result<Receipt, VeilidAPIError> {
|
||||
pub fn from_signed_data(crypto: Crypto, data: &[u8]) -> Result<Receipt, VeilidAPIError> {
|
||||
// Ensure we are at least the length of the envelope
|
||||
if data.len() < MIN_RECEIPT_SIZE {
|
||||
apibail_parse_error!("receipt too small", data.len());
|
||||
}
|
||||
|
||||
// Verify magic number
|
||||
let magic: [u8; 4] = data[0x00..0x04]
|
||||
let magic: [u8; 3] = data[0x00..0x03]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
if magic != *RECEIPT_MAGIC {
|
||||
@ -86,14 +83,24 @@ impl Receipt {
|
||||
}
|
||||
|
||||
// Check version
|
||||
let version = data[0x04];
|
||||
if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION {
|
||||
apibail_parse_error!("unsupported cryptography version", version);
|
||||
let version = data[0x03];
|
||||
if !VALID_ENVELOPE_VERSIONS.contains(&version) {
|
||||
apibail_parse_error!("unsupported envelope version", version);
|
||||
}
|
||||
|
||||
// Check crypto kind
|
||||
let crypto_kind = FourCC(
|
||||
data[0x04..0x08]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
let Some(vcrypto) = crypto.get(crypto_kind) else {
|
||||
apibail_parse_error!("unsupported crypto kind", crypto_kind);
|
||||
};
|
||||
|
||||
// Get size and ensure it matches the size of the envelope and is less than the maximum message size
|
||||
let size: u16 = u16::from_le_bytes(
|
||||
data[0x06..0x08]
|
||||
data[0x08..0x0A]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
@ -108,64 +115,80 @@ impl Receipt {
|
||||
}
|
||||
|
||||
// Get sender id
|
||||
let sender_id = DHTKey::new(
|
||||
data[0x20..0x40]
|
||||
let sender_id = PublicKey::new(
|
||||
data[0x22..0x42]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
|
||||
// Get signature
|
||||
let signature = DHTSignature::new(
|
||||
let signature = Signature::new(
|
||||
data[(data.len() - 64)..]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
|
||||
// Validate signature
|
||||
verify(&sender_id, &data[0..(data.len() - 64)], &signature)
|
||||
vcrypto
|
||||
.verify(&sender_id, &data[0..(data.len() - 64)], &signature)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
|
||||
// Get nonce
|
||||
let nonce: ReceiptNonce = data[0x08..0x20]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
let nonce: Nonce = Nonce::new(
|
||||
data[0x0A..0x22]
|
||||
.try_into()
|
||||
.map_err(VeilidAPIError::internal)?,
|
||||
);
|
||||
|
||||
// Get extra data and signature
|
||||
let extra_data: Vec<u8> = Vec::from(&data[0x40..(data.len() - 64)]);
|
||||
let extra_data: Vec<u8> = Vec::from(&data[0x42..(data.len() - 64)]);
|
||||
|
||||
// Return receipt
|
||||
Ok(Self {
|
||||
version,
|
||||
crypto_kind,
|
||||
nonce,
|
||||
sender_id,
|
||||
extra_data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_signed_data(&self, secret: &DHTKeySecret) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
pub fn to_signed_data(
|
||||
&self,
|
||||
crypto: Crypto,
|
||||
secret: &SecretKey,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
// Ensure extra data isn't too long
|
||||
let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE;
|
||||
if receipt_size > MAX_RECEIPT_SIZE {
|
||||
apibail_parse_error!("receipt too large", receipt_size);
|
||||
}
|
||||
// Get crypto version
|
||||
let vcrypto = crypto
|
||||
.get(self.crypto_kind)
|
||||
.expect("need to ensure only valid crypto kinds here");
|
||||
|
||||
let mut data: Vec<u8> = vec![0u8; receipt_size];
|
||||
|
||||
// Write magic
|
||||
data[0x00..0x04].copy_from_slice(RECEIPT_MAGIC);
|
||||
data[0x00..0x03].copy_from_slice(RECEIPT_MAGIC);
|
||||
// Write version
|
||||
data[0x04] = self.version;
|
||||
data[0x03] = self.version;
|
||||
// Write crypto kind
|
||||
data[0x04..0x08].copy_from_slice(&self.crypto_kind.0);
|
||||
// Write size
|
||||
data[0x06..0x08].copy_from_slice(&(receipt_size as u16).to_le_bytes());
|
||||
data[0x08..0x0A].copy_from_slice(&(receipt_size as u16).to_le_bytes());
|
||||
// Write nonce
|
||||
data[0x08..0x20].copy_from_slice(&self.nonce);
|
||||
data[0x0A..0x22].copy_from_slice(&self.nonce.bytes);
|
||||
// Write sender node id
|
||||
data[0x20..0x40].copy_from_slice(&self.sender_id.bytes);
|
||||
data[0x22..0x42].copy_from_slice(&self.sender_id.bytes);
|
||||
// Write extra data
|
||||
if !self.extra_data.is_empty() {
|
||||
data[0x40..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice());
|
||||
data[0x42..(receipt_size - 64)].copy_from_slice(self.extra_data.as_slice());
|
||||
}
|
||||
// Sign the receipt
|
||||
let signature = sign(&self.sender_id, secret, &data[0..(receipt_size - 64)])
|
||||
let signature = vcrypto
|
||||
.sign(&self.sender_id, secret, &data[0..(receipt_size - 64)])
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
// Append the signature
|
||||
data[(receipt_size - 64)..].copy_from_slice(&signature.bytes);
|
||||
@ -177,11 +200,15 @@ impl Receipt {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn get_nonce(&self) -> ReceiptNonce {
|
||||
pub fn get_crypto_kind(&self) -> CryptoKind {
|
||||
self.crypto_kind
|
||||
}
|
||||
|
||||
pub fn get_nonce(&self) -> Nonce {
|
||||
self.nonce
|
||||
}
|
||||
|
||||
pub fn get_sender_id(&self) -> DHTKey {
|
||||
pub fn get_sender_id(&self) -> PublicKey {
|
||||
self.sender_id
|
||||
}
|
||||
pub fn get_extra_data(&self) -> &[u8] {
|
||||
|
@ -1,5 +1,21 @@
|
||||
pub mod test_crypto;
|
||||
pub mod test_dht_key;
|
||||
pub mod test_envelope_receipt;
|
||||
pub mod test_types;
|
||||
|
||||
use super::*;
|
||||
use crate::tests::common::test_veilid_config::*;
|
||||
|
||||
async fn crypto_tests_startup() -> VeilidAPI {
|
||||
trace!("crypto_tests: starting");
|
||||
let (update_callback, config_callback) = setup_veilid_core();
|
||||
let api = api_startup(update_callback, config_callback)
|
||||
.await
|
||||
.expect("startup failed");
|
||||
api
|
||||
}
|
||||
|
||||
async fn crypto_tests_shutdown(api: VeilidAPI) {
|
||||
trace!("crypto_tests: shutting down");
|
||||
api.shutdown().await;
|
||||
trace!("crypto_tests: finished");
|
||||
}
|
||||
|
@ -1,37 +1,21 @@
|
||||
use super::*;
|
||||
use crate::tests::common::test_veilid_config::*;
|
||||
|
||||
static LOREM_IPSUM:&[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
|
||||
|
||||
async fn startup() -> VeilidAPI {
|
||||
trace!("test_table_store: starting");
|
||||
let (update_callback, config_callback) = setup_veilid_core();
|
||||
let api = api_startup(update_callback, config_callback)
|
||||
.await
|
||||
.expect("startup failed");
|
||||
api
|
||||
}
|
||||
|
||||
async fn shutdown(api: VeilidAPI) {
|
||||
trace!("test_table_store: shutting down");
|
||||
api.shutdown().await;
|
||||
trace!("test_table_store: finished");
|
||||
}
|
||||
|
||||
pub async fn test_aead() {
|
||||
pub async fn test_aead(vcrypto: CryptoSystemVersion) {
|
||||
trace!("test_aead");
|
||||
|
||||
let n1 = Crypto::get_random_nonce();
|
||||
let n1 = vcrypto.random_nonce();
|
||||
let n2 = loop {
|
||||
let n = Crypto::get_random_nonce();
|
||||
let n = vcrypto.random_nonce();
|
||||
if n != n1 {
|
||||
break n;
|
||||
}
|
||||
};
|
||||
|
||||
let ss1 = Crypto::get_random_secret();
|
||||
let ss1 = vcrypto.random_shared_secret();
|
||||
let ss2 = loop {
|
||||
let ss = Crypto::get_random_secret();
|
||||
let ss = vcrypto.random_shared_secret();
|
||||
if ss != ss1 {
|
||||
break ss;
|
||||
}
|
||||
@ -41,67 +25,77 @@ pub async fn test_aead() {
|
||||
let body2 = body.clone();
|
||||
let size_before_encrypt = body.len();
|
||||
assert!(
|
||||
Crypto::encrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
|
||||
vcrypto
|
||||
.encrypt_in_place_aead(&mut body, &n1, &ss1, None)
|
||||
.is_ok(),
|
||||
"encrypt should succeed"
|
||||
);
|
||||
let size_after_encrypt = body.len();
|
||||
assert!(
|
||||
size_after_encrypt - size_before_encrypt == AEAD_OVERHEAD,
|
||||
size_after_encrypt - size_before_encrypt == vcrypto.aead_overhead(),
|
||||
"overhead should match"
|
||||
);
|
||||
let mut body3 = body.clone();
|
||||
let mut body4 = body.clone();
|
||||
let mut body5 = body.clone();
|
||||
assert!(
|
||||
Crypto::decrypt_in_place_aead(&mut body, &n1, &ss1, None).is_ok(),
|
||||
vcrypto
|
||||
.decrypt_in_place_aead(&mut body, &n1, &ss1, None)
|
||||
.is_ok(),
|
||||
"decrypt should succeed"
|
||||
);
|
||||
assert_eq!(body, body2, "results should be the same");
|
||||
|
||||
assert!(
|
||||
Crypto::decrypt_in_place_aead(&mut body3, &n2, &ss1, None).is_err(),
|
||||
vcrypto
|
||||
.decrypt_in_place_aead(&mut body3, &n2, &ss1, None)
|
||||
.is_err(),
|
||||
"decrypt with wrong nonce should fail"
|
||||
);
|
||||
assert_ne!(body3, body, "failure changes data");
|
||||
|
||||
assert!(
|
||||
Crypto::decrypt_in_place_aead(&mut body4, &n1, &ss2, None).is_err(),
|
||||
vcrypto
|
||||
.decrypt_in_place_aead(&mut body4, &n1, &ss2, None)
|
||||
.is_err(),
|
||||
"decrypt with wrong secret should fail"
|
||||
);
|
||||
assert_ne!(body4, body, "failure changes data");
|
||||
|
||||
assert!(
|
||||
Crypto::decrypt_in_place_aead(&mut body5, &n1, &ss2, Some(b"foobar")).is_err(),
|
||||
vcrypto
|
||||
.decrypt_in_place_aead(&mut body5, &n1, &ss2, Some(b"foobar"))
|
||||
.is_err(),
|
||||
"decrypt with wrong associated data should fail"
|
||||
);
|
||||
assert_ne!(body5, body, "failure changes data");
|
||||
|
||||
assert!(
|
||||
Crypto::decrypt_aead(LOREM_IPSUM, &n1, &ss1, None).is_err(),
|
||||
vcrypto.decrypt_aead(LOREM_IPSUM, &n1, &ss1, None).is_err(),
|
||||
"should fail authentication"
|
||||
);
|
||||
|
||||
let body5 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
|
||||
let body6 = Crypto::decrypt_aead(&body5, &n1, &ss1, None).unwrap();
|
||||
let body7 = Crypto::encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
|
||||
let body5 = vcrypto.encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
|
||||
let body6 = vcrypto.decrypt_aead(&body5, &n1, &ss1, None).unwrap();
|
||||
let body7 = vcrypto.encrypt_aead(LOREM_IPSUM, &n1, &ss1, None).unwrap();
|
||||
assert_eq!(body6, LOREM_IPSUM);
|
||||
assert_eq!(body5, body7);
|
||||
}
|
||||
|
||||
pub async fn test_no_auth() {
|
||||
pub async fn test_no_auth(vcrypto: CryptoSystemVersion) {
|
||||
trace!("test_no_auth");
|
||||
|
||||
let n1 = Crypto::get_random_nonce();
|
||||
let n1 = vcrypto.random_nonce();
|
||||
let n2 = loop {
|
||||
let n = Crypto::get_random_nonce();
|
||||
let n = vcrypto.random_nonce();
|
||||
if n != n1 {
|
||||
break n;
|
||||
}
|
||||
};
|
||||
|
||||
let ss1 = Crypto::get_random_secret();
|
||||
let ss1 = vcrypto.random_shared_secret();
|
||||
let ss2 = loop {
|
||||
let ss = Crypto::get_random_secret();
|
||||
let ss = vcrypto.random_shared_secret();
|
||||
if ss != ss1 {
|
||||
break ss;
|
||||
}
|
||||
@ -110,7 +104,7 @@ pub async fn test_no_auth() {
|
||||
let mut body = LOREM_IPSUM.to_vec();
|
||||
let body2 = body.clone();
|
||||
let size_before_encrypt = body.len();
|
||||
Crypto::crypt_in_place_no_auth(&mut body, &n1, &ss1);
|
||||
vcrypto.crypt_in_place_no_auth(&mut body, &n1, &ss1);
|
||||
|
||||
let size_after_encrypt = body.len();
|
||||
assert_eq!(
|
||||
@ -120,41 +114,47 @@ pub async fn test_no_auth() {
|
||||
let mut body3 = body.clone();
|
||||
let mut body4 = body.clone();
|
||||
|
||||
Crypto::crypt_in_place_no_auth(&mut body, &n1, &ss1);
|
||||
vcrypto.crypt_in_place_no_auth(&mut body, &n1, &ss1);
|
||||
assert_eq!(body, body2, "result after decrypt should be the same");
|
||||
|
||||
Crypto::crypt_in_place_no_auth(&mut body3, &n2, &ss1);
|
||||
vcrypto.crypt_in_place_no_auth(&mut body3, &n2, &ss1);
|
||||
assert_ne!(body3, body, "decrypt should not be equal with wrong nonce");
|
||||
|
||||
Crypto::crypt_in_place_no_auth(&mut body4, &n1, &ss2);
|
||||
vcrypto.crypt_in_place_no_auth(&mut body4, &n1, &ss2);
|
||||
assert_ne!(body4, body, "decrypt should not be equal with wrong secret");
|
||||
|
||||
let body5 = Crypto::crypt_no_auth(LOREM_IPSUM, &n1, &ss1);
|
||||
let body6 = Crypto::crypt_no_auth(&body5, &n1, &ss1);
|
||||
let body7 = Crypto::crypt_no_auth(LOREM_IPSUM, &n1, &ss1);
|
||||
let body5 = vcrypto.crypt_no_auth_unaligned(LOREM_IPSUM, &n1, &ss1);
|
||||
let body6 = vcrypto.crypt_no_auth_unaligned(&body5, &n1, &ss1);
|
||||
let body7 = vcrypto.crypt_no_auth_unaligned(LOREM_IPSUM, &n1, &ss1);
|
||||
assert_eq!(body6, LOREM_IPSUM);
|
||||
assert_eq!(body5, body7);
|
||||
|
||||
let body5 = vcrypto.crypt_no_auth_aligned_8(LOREM_IPSUM, &n1, &ss1);
|
||||
let body6 = vcrypto.crypt_no_auth_aligned_8(&body5, &n1, &ss1);
|
||||
let body7 = vcrypto.crypt_no_auth_aligned_8(LOREM_IPSUM, &n1, &ss1);
|
||||
assert_eq!(body6, LOREM_IPSUM);
|
||||
assert_eq!(body5, body7);
|
||||
}
|
||||
|
||||
pub async fn test_dh(crypto: Crypto) {
|
||||
pub async fn test_dh(vcrypto: CryptoSystemVersion) {
|
||||
trace!("test_dh");
|
||||
let (dht_key, dht_key_secret) = key::generate_secret();
|
||||
let (dht_key2, dht_key_secret2) = key::generate_secret();
|
||||
let (dht_key, dht_key_secret) = vcrypto.generate_keypair().into_split();
|
||||
let (dht_key2, dht_key_secret2) = vcrypto.generate_keypair().into_split();
|
||||
|
||||
let r1 = Crypto::compute_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r2 = Crypto::compute_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r3 = Crypto::compute_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r4 = Crypto::compute_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r1 = vcrypto.compute_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r2 = vcrypto.compute_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r3 = vcrypto.compute_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r4 = vcrypto.compute_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
assert_eq!(r1, r2);
|
||||
assert_eq!(r3, r4);
|
||||
assert_eq!(r2, r3);
|
||||
trace!("dh: {:?}", r1);
|
||||
|
||||
// test cache
|
||||
let r5 = crypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r6 = crypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r7 = crypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r8 = crypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r5 = vcrypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r6 = vcrypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
let r7 = vcrypto.cached_dh(&dht_key, &dht_key_secret2).unwrap();
|
||||
let r8 = vcrypto.cached_dh(&dht_key2, &dht_key_secret).unwrap();
|
||||
assert_eq!(r1, r5);
|
||||
assert_eq!(r2, r6);
|
||||
assert_eq!(r3, r7);
|
||||
@ -163,11 +163,17 @@ pub async fn test_dh(crypto: Crypto) {
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
let api = startup().await;
|
||||
let api = crypto_tests_startup().await;
|
||||
let crypto = api.crypto().unwrap();
|
||||
test_aead().await;
|
||||
test_no_auth().await;
|
||||
test_dh(crypto).await;
|
||||
shutdown(api.clone()).await;
|
||||
|
||||
// Test versions
|
||||
for v in VALID_CRYPTO_KINDS {
|
||||
let vcrypto = crypto.get(v).unwrap();
|
||||
test_aead(vcrypto.clone()).await;
|
||||
test_no_auth(vcrypto.clone()).await;
|
||||
test_dh(vcrypto).await;
|
||||
}
|
||||
|
||||
crypto_tests_shutdown(api.clone()).await;
|
||||
assert!(api.is_shutdown());
|
||||
}
|
||||
|
@ -1,304 +0,0 @@
|
||||
#![allow(clippy::bool_assert_comparison)]
|
||||
|
||||
use super::*;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
static LOREM_IPSUM:&str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
|
||||
static CHEEZBURGER: &str = "I can has cheezburger";
|
||||
static EMPTY_KEY: [u8; key::DHT_KEY_LENGTH] = [0u8; key::DHT_KEY_LENGTH];
|
||||
static EMPTY_KEY_SECRET: [u8; key::DHT_KEY_SECRET_LENGTH] = [0u8; key::DHT_KEY_SECRET_LENGTH];
|
||||
|
||||
pub async fn test_generate_secret() {
|
||||
// Verify keys generate
|
||||
let (dht_key, dht_key_secret) = key::generate_secret();
|
||||
let (dht_key2, dht_key_secret2) = key::generate_secret();
|
||||
|
||||
// Verify byte patterns are different between public and secret
|
||||
assert_ne!(dht_key.bytes, dht_key_secret.bytes);
|
||||
assert_ne!(dht_key2.bytes, dht_key_secret2.bytes);
|
||||
|
||||
// Verify the keys and secrets are different across keypairs
|
||||
assert_ne!(dht_key, dht_key2);
|
||||
assert_ne!(dht_key_secret, dht_key_secret2);
|
||||
}
|
||||
|
||||
pub async fn test_sign_and_verify() {
|
||||
// Make two keys
|
||||
let (dht_key, dht_key_secret) = key::generate_secret();
|
||||
let (dht_key2, dht_key_secret2) = key::generate_secret();
|
||||
// Sign the same message twice
|
||||
let dht_sig = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
trace!("dht_sig: {:?}", dht_sig);
|
||||
let dht_sig_b = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
// Sign a second message
|
||||
let dht_sig_c = key::sign(&dht_key, &dht_key_secret, CHEEZBURGER.as_bytes()).unwrap();
|
||||
trace!("dht_sig_c: {:?}", dht_sig_c);
|
||||
// Verify they are the same signature
|
||||
assert_eq!(dht_sig, dht_sig_b);
|
||||
// Sign the same message with a different key
|
||||
let dht_sig2 = key::sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
// Verify a different key gives a different signature
|
||||
assert_ne!(dht_sig2, dht_sig_b);
|
||||
|
||||
// Try using the wrong secret to sign
|
||||
let a1 = key::sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
let a2 = key::sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
let b1 = key::sign(&dht_key, &dht_key_secret2, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
let b2 = key::sign(&dht_key2, &dht_key_secret, LOREM_IPSUM.as_bytes()).unwrap();
|
||||
assert_ne!(a1, b1);
|
||||
assert_ne!(a2, b2);
|
||||
assert_ne!(a1, b2);
|
||||
assert_ne!(a2, b1);
|
||||
assert_ne!(a1, a2);
|
||||
assert_ne!(b1, b2);
|
||||
assert_ne!(a1, b2);
|
||||
assert_ne!(b1, a2);
|
||||
|
||||
assert_eq!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1), Ok(()));
|
||||
assert_eq!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a2), Ok(()));
|
||||
assert!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &b1).is_err());
|
||||
assert!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &b2).is_err());
|
||||
|
||||
// Try verifications that should work
|
||||
assert_eq!(
|
||||
key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
key::verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c),
|
||||
Ok(())
|
||||
);
|
||||
// Try verifications that shouldn't work
|
||||
assert!(key::verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig).is_err());
|
||||
assert!(key::verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2).is_err());
|
||||
assert!(key::verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c).is_err());
|
||||
assert!(key::verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig).is_err());
|
||||
}
|
||||
|
||||
pub async fn test_key_conversions() {
|
||||
// Test default key
|
||||
let (dht_key, dht_key_secret) = (key::DHTKey::default(), key::DHTKeySecret::default());
|
||||
assert_eq!(dht_key.bytes, EMPTY_KEY);
|
||||
assert_eq!(dht_key_secret.bytes, EMPTY_KEY_SECRET);
|
||||
let dht_key_string = String::from(&dht_key);
|
||||
trace!("dht_key_string: {:?}", dht_key_string);
|
||||
let dht_key_string2 = String::from(&dht_key);
|
||||
trace!("dht_key_string2: {:?}", dht_key_string2);
|
||||
assert_eq!(dht_key_string, dht_key_string2);
|
||||
|
||||
let dht_key_secret_string = String::from(&dht_key_secret);
|
||||
trace!("dht_key_secret_string: {:?}", dht_key_secret_string);
|
||||
assert_eq!(dht_key_secret_string, dht_key_string);
|
||||
|
||||
// Make different keys
|
||||
let (dht_key2, dht_key_secret2) = key::generate_secret();
|
||||
trace!("dht_key2: {:?}", dht_key2);
|
||||
trace!("dht_key_secret2: {:?}", dht_key_secret2);
|
||||
let (dht_key3, _dht_key_secret3) = key::generate_secret();
|
||||
trace!("dht_key3: {:?}", dht_key3);
|
||||
trace!("_dht_key_secret3: {:?}", _dht_key_secret3);
|
||||
|
||||
let dht_key2_string = String::from(&dht_key2);
|
||||
let dht_key2_string2 = String::from(&dht_key2);
|
||||
let dht_key3_string = String::from(&dht_key3);
|
||||
assert_eq!(dht_key2_string, dht_key2_string2);
|
||||
assert_ne!(dht_key3_string, dht_key2_string);
|
||||
let dht_key_secret2_string = String::from(&dht_key_secret2);
|
||||
assert_ne!(dht_key_secret2_string, dht_key_secret_string);
|
||||
assert_ne!(dht_key_secret2_string, dht_key2_string);
|
||||
|
||||
// Assert they convert back correctly
|
||||
let dht_key_back = key::DHTKey::try_from(dht_key_string.as_str()).unwrap();
|
||||
let dht_key_back2 = key::DHTKey::try_from(dht_key_string2.as_str()).unwrap();
|
||||
assert_eq!(dht_key_back, dht_key_back2);
|
||||
assert_eq!(dht_key_back, dht_key);
|
||||
assert_eq!(dht_key_back2, dht_key);
|
||||
|
||||
let dht_key_secret_back = key::DHTKeySecret::try_from(dht_key_secret_string.as_str()).unwrap();
|
||||
assert_eq!(dht_key_secret_back, dht_key_secret);
|
||||
|
||||
let dht_key2_back = key::DHTKey::try_from(dht_key2_string.as_str()).unwrap();
|
||||
let dht_key2_back2 = key::DHTKey::try_from(dht_key2_string2.as_str()).unwrap();
|
||||
assert_eq!(dht_key2_back, dht_key2_back2);
|
||||
assert_eq!(dht_key2_back, dht_key2);
|
||||
assert_eq!(dht_key2_back2, dht_key2);
|
||||
|
||||
let dht_key_secret2_back =
|
||||
key::DHTKeySecret::try_from(dht_key_secret2_string.as_str()).unwrap();
|
||||
assert_eq!(dht_key_secret2_back, dht_key_secret2);
|
||||
|
||||
// Assert string roundtrip
|
||||
assert_eq!(String::from(&dht_key2_back), dht_key2_string);
|
||||
// These conversions should fail
|
||||
assert!(key::DHTKey::try_from("whatever").is_err());
|
||||
assert!(key::DHTKeySecret::try_from("whatever").is_err());
|
||||
assert!(key::DHTKey::try_from("").is_err());
|
||||
assert!(key::DHTKeySecret::try_from("").is_err());
|
||||
assert!(key::DHTKey::try_from(" ").is_err());
|
||||
assert!(key::DHTKeySecret::try_from(" ").is_err());
|
||||
assert!(key::DHTKey::try_from(
|
||||
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
|
||||
)
|
||||
.is_err());
|
||||
assert!(key::DHTKeySecret::try_from(
|
||||
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
pub async fn test_encode_decode() {
|
||||
let dht_key = key::DHTKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
|
||||
let dht_key_secret =
|
||||
key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
|
||||
let dht_key_b = key::DHTKey::new(EMPTY_KEY);
|
||||
let dht_key_secret_b = key::DHTKeySecret::new(EMPTY_KEY_SECRET);
|
||||
assert_eq!(dht_key, dht_key_b);
|
||||
assert_eq!(dht_key_secret, dht_key_secret_b);
|
||||
|
||||
let (dht_key2, dht_key_secret2) = key::generate_secret();
|
||||
|
||||
let e1 = dht_key.encode();
|
||||
trace!("e1: {:?}", e1);
|
||||
assert_eq!(e1, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned());
|
||||
let e1s = dht_key_secret.encode();
|
||||
trace!("e1s: {:?}", e1s);
|
||||
let e2 = dht_key2.encode();
|
||||
trace!("e2: {:?}", e2);
|
||||
let e2s = dht_key_secret2.encode();
|
||||
trace!("e2s: {:?}", e2s);
|
||||
|
||||
let d1 = key::DHTKey::try_decode(e1.as_str()).unwrap();
|
||||
trace!("d1: {:?}", d1);
|
||||
assert_eq!(dht_key, d1);
|
||||
|
||||
let d1s = key::DHTKeySecret::try_decode(e1s.as_str()).unwrap();
|
||||
trace!("d1s: {:?}", d1s);
|
||||
assert_eq!(dht_key_secret, d1s);
|
||||
|
||||
let d2 = key::DHTKey::try_decode(e2.as_str()).unwrap();
|
||||
trace!("d2: {:?}", d2);
|
||||
assert_eq!(dht_key2, d2);
|
||||
|
||||
let d2s = key::DHTKeySecret::try_decode(e2s.as_str()).unwrap();
|
||||
trace!("d2s: {:?}", d2s);
|
||||
assert_eq!(dht_key_secret2, d2s);
|
||||
|
||||
// Failures
|
||||
let f1 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
assert!(f1.is_err());
|
||||
let f2 = key::DHTKeySecret::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&");
|
||||
assert!(f2.is_err());
|
||||
}
|
||||
|
||||
async fn test_hash() {
|
||||
let mut s = BTreeSet::<key::DHTKey>::new();
|
||||
|
||||
let k1 = key::generate_hash("abc".as_bytes());
|
||||
let k2 = key::generate_hash("abcd".as_bytes());
|
||||
let k3 = key::generate_hash("".as_bytes());
|
||||
let k4 = key::generate_hash(" ".as_bytes());
|
||||
let k5 = key::generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let k6 = key::generate_hash(CHEEZBURGER.as_bytes());
|
||||
|
||||
s.insert(k1);
|
||||
s.insert(k2);
|
||||
s.insert(k3);
|
||||
s.insert(k4);
|
||||
s.insert(k5);
|
||||
s.insert(k6);
|
||||
assert_eq!(s.len(), 6);
|
||||
|
||||
let v1 = key::generate_hash("abc".as_bytes());
|
||||
let v2 = key::generate_hash("abcd".as_bytes());
|
||||
let v3 = key::generate_hash("".as_bytes());
|
||||
let v4 = key::generate_hash(" ".as_bytes());
|
||||
let v5 = key::generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let v6 = key::generate_hash(CHEEZBURGER.as_bytes());
|
||||
|
||||
assert_eq!(k1, v1);
|
||||
assert_eq!(k2, v2);
|
||||
assert_eq!(k3, v3);
|
||||
assert_eq!(k4, v4);
|
||||
assert_eq!(k5, v5);
|
||||
assert_eq!(k6, v6);
|
||||
|
||||
key::validate_hash("abc".as_bytes(), &v1);
|
||||
key::validate_hash("abcd".as_bytes(), &v2);
|
||||
key::validate_hash("".as_bytes(), &v3);
|
||||
key::validate_hash(" ".as_bytes(), &v4);
|
||||
key::validate_hash(LOREM_IPSUM.as_bytes(), &v5);
|
||||
key::validate_hash(CHEEZBURGER.as_bytes(), &v6);
|
||||
}
|
||||
|
||||
async fn test_operations() {
|
||||
let k1 = key::generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let k2 = key::generate_hash(CHEEZBURGER.as_bytes());
|
||||
let k3 = key::generate_hash("abc".as_bytes());
|
||||
|
||||
// Get distance
|
||||
let d1 = key::distance(&k1, &k2);
|
||||
let d2 = key::distance(&k2, &k1);
|
||||
let d3 = key::distance(&k1, &k3);
|
||||
let d4 = key::distance(&k2, &k3);
|
||||
|
||||
trace!("d1={:?}", d1);
|
||||
trace!("d2={:?}", d2);
|
||||
trace!("d3={:?}", d3);
|
||||
trace!("d4={:?}", d4);
|
||||
|
||||
// Verify commutativity
|
||||
assert_eq!(d1, d2);
|
||||
assert!(d1 <= d2);
|
||||
assert!(d1 >= d2);
|
||||
assert!(d1 >= d2);
|
||||
assert!(d1 <= d2);
|
||||
assert_eq!(d2, d1);
|
||||
assert!(d2 <= d1);
|
||||
assert!(d2 >= d1);
|
||||
assert!(d2 >= d1);
|
||||
assert!(d2 <= d1);
|
||||
|
||||
// Verify nibbles
|
||||
assert_eq!(d1.nibble(0), 0x9u8);
|
||||
assert_eq!(d1.nibble(1), 0x4u8);
|
||||
assert_eq!(d1.nibble(2), 0x3u8);
|
||||
assert_eq!(d1.nibble(3), 0x6u8);
|
||||
assert_eq!(d1.nibble(63), 0x6u8);
|
||||
|
||||
assert_eq!(d1.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
assert_eq!(d2.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
assert_eq!(d3.first_nonzero_nibble(), Some((1, 0x4u8)));
|
||||
assert_eq!(d4.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
|
||||
// Verify bits
|
||||
assert_eq!(d1.bit(0), true);
|
||||
assert_eq!(d1.bit(1), false);
|
||||
assert_eq!(d1.bit(7), false);
|
||||
assert_eq!(d1.bit(8), false);
|
||||
assert_eq!(d1.bit(14), true);
|
||||
assert_eq!(d1.bit(15), false);
|
||||
assert_eq!(d1.bit(254), true);
|
||||
assert_eq!(d1.bit(255), false);
|
||||
|
||||
assert_eq!(d1.first_nonzero_bit(), Some(0));
|
||||
assert_eq!(d2.first_nonzero_bit(), Some(0));
|
||||
assert_eq!(d3.first_nonzero_bit(), Some(5));
|
||||
assert_eq!(d4.first_nonzero_bit(), Some(0));
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
test_generate_secret().await;
|
||||
test_sign_and_verify().await;
|
||||
test_key_conversions().await;
|
||||
test_encode_decode().await;
|
||||
test_hash().await;
|
||||
test_operations().await;
|
||||
}
|
@ -1,37 +1,39 @@
|
||||
use super::*;
|
||||
use crate::tests::common::test_veilid_config::*;
|
||||
|
||||
pub async fn test_envelope_round_trip() {
|
||||
pub async fn test_envelope_round_trip(
|
||||
envelope_version: EnvelopeVersion,
|
||||
vcrypto: CryptoSystemVersion,
|
||||
) {
|
||||
info!("--- test envelope round trip ---");
|
||||
let (update_callback, config_callback) = setup_veilid_core();
|
||||
let api = api_startup(update_callback, config_callback)
|
||||
.await
|
||||
.expect("startup failed");
|
||||
|
||||
// Get crypto
|
||||
let crypto = api.crypto().unwrap();
|
||||
|
||||
// Create envelope
|
||||
let ts = Timestamp::from(0x12345678ABCDEF69u64);
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let (sender_id, sender_secret) = generate_secret();
|
||||
let (recipient_id, recipient_secret) = generate_secret();
|
||||
let envelope = Envelope::new(0, ts, nonce, sender_id, recipient_id);
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let (sender_id, sender_secret) = vcrypto.generate_keypair().into_split();
|
||||
let (recipient_id, recipient_secret) = vcrypto.generate_keypair().into_split();
|
||||
let envelope = Envelope::new(
|
||||
envelope_version,
|
||||
vcrypto.kind(),
|
||||
ts,
|
||||
nonce,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
);
|
||||
|
||||
// Create arbitrary body
|
||||
let body = b"This is an arbitrary body";
|
||||
|
||||
// Serialize to bytes
|
||||
let enc_data = envelope
|
||||
.to_encrypted_data(crypto.clone(), body, &sender_secret)
|
||||
.to_encrypted_data(vcrypto.crypto(), body, &sender_secret)
|
||||
.expect("failed to encrypt data");
|
||||
|
||||
// Deserialize from bytes
|
||||
let envelope2 =
|
||||
Envelope::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
|
||||
let envelope2 = Envelope::from_signed_data(vcrypto.crypto(), &enc_data)
|
||||
.expect("failed to deserialize envelope from data");
|
||||
|
||||
let body2 = envelope2
|
||||
.decrypt_body(crypto.clone(), &enc_data, &recipient_secret)
|
||||
.decrypt_body(vcrypto.crypto(), &enc_data, &recipient_secret)
|
||||
.expect("failed to decrypt envelope body");
|
||||
|
||||
// Compare envelope and body
|
||||
@ -43,41 +45,43 @@ pub async fn test_envelope_round_trip() {
|
||||
let mut mod_enc_data = enc_data.clone();
|
||||
mod_enc_data[enc_data_len - 1] ^= 0x80u8;
|
||||
assert!(
|
||||
Envelope::from_signed_data(&mod_enc_data).is_err(),
|
||||
Envelope::from_signed_data(vcrypto.crypto(), &mod_enc_data).is_err(),
|
||||
"should have failed to decode envelope with modified signature"
|
||||
);
|
||||
let mut mod_enc_data2 = enc_data.clone();
|
||||
mod_enc_data2[enc_data_len - 65] ^= 0x80u8;
|
||||
assert!(
|
||||
Envelope::from_signed_data(&mod_enc_data2).is_err(),
|
||||
Envelope::from_signed_data(vcrypto.crypto(), &mod_enc_data2).is_err(),
|
||||
"should have failed to decode envelope with modified data"
|
||||
);
|
||||
|
||||
api.shutdown().await;
|
||||
}
|
||||
|
||||
pub async fn test_receipt_round_trip() {
|
||||
pub async fn test_receipt_round_trip(
|
||||
envelope_version: EnvelopeVersion,
|
||||
vcrypto: CryptoSystemVersion,
|
||||
) {
|
||||
info!("--- test receipt round trip ---");
|
||||
// Create arbitrary body
|
||||
let body = b"This is an arbitrary body";
|
||||
|
||||
// Create receipt
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let (sender_id, sender_secret) = generate_secret();
|
||||
let receipt = Receipt::try_new(0, nonce, sender_id, body).expect("should not fail");
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let (sender_id, sender_secret) = vcrypto.generate_keypair().into_split();
|
||||
let receipt = Receipt::try_new(envelope_version, vcrypto.kind(), nonce, sender_id, body)
|
||||
.expect("should not fail");
|
||||
|
||||
// Serialize to bytes
|
||||
let mut enc_data = receipt
|
||||
.to_signed_data(&sender_secret)
|
||||
.to_signed_data(vcrypto.crypto(), &sender_secret)
|
||||
.expect("failed to make signed data");
|
||||
|
||||
// Deserialize from bytes
|
||||
let receipt2 =
|
||||
Receipt::from_signed_data(&enc_data).expect("failed to deserialize envelope from data");
|
||||
let receipt2 = Receipt::from_signed_data(vcrypto.crypto(), &enc_data)
|
||||
.expect("failed to deserialize envelope from data");
|
||||
|
||||
// Should not validate even when a single bit is changed
|
||||
enc_data[5] = 0x01;
|
||||
Receipt::from_signed_data(&enc_data)
|
||||
Receipt::from_signed_data(vcrypto.crypto(), &enc_data)
|
||||
.expect_err("should have failed to decrypt using wrong secret");
|
||||
|
||||
// Compare receipts
|
||||
@ -85,6 +89,19 @@ pub async fn test_receipt_round_trip() {
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
test_envelope_round_trip().await;
|
||||
test_receipt_round_trip().await;
|
||||
let api = crypto_tests_startup().await;
|
||||
let crypto = api.crypto().unwrap();
|
||||
|
||||
// Test versions
|
||||
for ev in VALID_ENVELOPE_VERSIONS {
|
||||
for v in VALID_CRYPTO_KINDS {
|
||||
let vcrypto = crypto.get(v).unwrap();
|
||||
|
||||
test_envelope_round_trip(ev, vcrypto.clone()).await;
|
||||
test_receipt_round_trip(ev, vcrypto).await;
|
||||
}
|
||||
}
|
||||
|
||||
crypto_tests_shutdown(api.clone()).await;
|
||||
assert!(api.is_shutdown());
|
||||
}
|
||||
|
348
veilid-core/src/crypto/tests/test_types.rs
Normal file
348
veilid-core/src/crypto/tests/test_types.rs
Normal file
@ -0,0 +1,348 @@
|
||||
#![allow(clippy::bool_assert_comparison)]
|
||||
|
||||
use super::*;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
static LOREM_IPSUM:&str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
|
||||
static CHEEZBURGER: &str = "I can has cheezburger";
|
||||
static EMPTY_KEY: [u8; PUBLIC_KEY_LENGTH] = [0u8; PUBLIC_KEY_LENGTH];
|
||||
static EMPTY_KEY_SECRET: [u8; SECRET_KEY_LENGTH] = [0u8; SECRET_KEY_LENGTH];
|
||||
|
||||
pub async fn test_generate_secret(vcrypto: CryptoSystemVersion) {
|
||||
// Verify keys generate
|
||||
let (dht_key, dht_key_secret) = vcrypto.generate_keypair().into_split();
|
||||
let (dht_key2, dht_key_secret2) = vcrypto.generate_keypair().into_split();
|
||||
|
||||
// Verify byte patterns are different between public and secret
|
||||
assert_ne!(dht_key.bytes, dht_key_secret.bytes);
|
||||
assert_ne!(dht_key2.bytes, dht_key_secret2.bytes);
|
||||
|
||||
// Verify the keys and secrets are different across keypairs
|
||||
assert_ne!(dht_key, dht_key2);
|
||||
assert_ne!(dht_key_secret, dht_key_secret2);
|
||||
}
|
||||
|
||||
pub async fn test_sign_and_verify(vcrypto: CryptoSystemVersion) {
|
||||
// Make two keys
|
||||
let (dht_key, dht_key_secret) = vcrypto.generate_keypair().into_split();
|
||||
let (dht_key2, dht_key_secret2) = vcrypto.generate_keypair().into_split();
|
||||
// Sign the same message twice
|
||||
let dht_sig = vcrypto
|
||||
.sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
trace!("dht_sig: {:?}", dht_sig);
|
||||
let dht_sig_b = vcrypto
|
||||
.sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
// Sign a second message
|
||||
let dht_sig_c = vcrypto
|
||||
.sign(&dht_key, &dht_key_secret, CHEEZBURGER.as_bytes())
|
||||
.unwrap();
|
||||
trace!("dht_sig_c: {:?}", dht_sig_c);
|
||||
// Verify they are the same signature
|
||||
assert_eq!(dht_sig, dht_sig_b);
|
||||
// Sign the same message with a different key
|
||||
let dht_sig2 = vcrypto
|
||||
.sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
// Verify a different key gives a different signature
|
||||
assert_ne!(dht_sig2, dht_sig_b);
|
||||
|
||||
// Try using the wrong secret to sign
|
||||
let a1 = vcrypto
|
||||
.sign(&dht_key, &dht_key_secret, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
let a2 = vcrypto
|
||||
.sign(&dht_key2, &dht_key_secret2, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
let b1 = vcrypto
|
||||
.sign(&dht_key, &dht_key_secret2, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
let b2 = vcrypto
|
||||
.sign(&dht_key2, &dht_key_secret, LOREM_IPSUM.as_bytes())
|
||||
.unwrap();
|
||||
assert_ne!(a1, b1);
|
||||
assert_ne!(a2, b2);
|
||||
assert_ne!(a1, b2);
|
||||
assert_ne!(a2, b1);
|
||||
assert_ne!(a1, a2);
|
||||
assert_ne!(b1, b2);
|
||||
assert_ne!(a1, b2);
|
||||
assert_ne!(b1, a2);
|
||||
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a2),
|
||||
Ok(())
|
||||
);
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key, LOREM_IPSUM.as_bytes(), &b1)
|
||||
.is_err());
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &b2)
|
||||
.is_err());
|
||||
|
||||
// Try verifications that should work
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c),
|
||||
Ok(())
|
||||
);
|
||||
// Try verifications that shouldn't work
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig)
|
||||
.is_err());
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2)
|
||||
.is_err());
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c)
|
||||
.is_err());
|
||||
assert!(vcrypto
|
||||
.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) {
|
||||
// Test default key
|
||||
let (dht_key, dht_key_secret) = (PublicKey::default(), SecretKey::default());
|
||||
assert_eq!(dht_key.bytes, EMPTY_KEY);
|
||||
assert_eq!(dht_key_secret.bytes, EMPTY_KEY_SECRET);
|
||||
let dht_key_string = String::from(&dht_key);
|
||||
trace!("dht_key_string: {:?}", dht_key_string);
|
||||
let dht_key_string2 = String::from(&dht_key);
|
||||
trace!("dht_key_string2: {:?}", dht_key_string2);
|
||||
assert_eq!(dht_key_string, dht_key_string2);
|
||||
|
||||
let dht_key_secret_string = String::from(&dht_key_secret);
|
||||
trace!("dht_key_secret_string: {:?}", dht_key_secret_string);
|
||||
assert_eq!(dht_key_secret_string, dht_key_string);
|
||||
|
||||
// Make different keys
|
||||
let (dht_key2, dht_key_secret2) = vcrypto.generate_keypair().into_split();
|
||||
trace!("dht_key2: {:?}", dht_key2);
|
||||
trace!("dht_key_secret2: {:?}", dht_key_secret2);
|
||||
let (dht_key3, _dht_key_secret3) = vcrypto.generate_keypair().into_split();
|
||||
trace!("dht_key3: {:?}", dht_key3);
|
||||
trace!("_dht_key_secret3: {:?}", _dht_key_secret3);
|
||||
|
||||
let dht_key2_string = String::from(&dht_key2);
|
||||
let dht_key2_string2 = String::from(&dht_key2);
|
||||
let dht_key3_string = String::from(&dht_key3);
|
||||
assert_eq!(dht_key2_string, dht_key2_string2);
|
||||
assert_ne!(dht_key3_string, dht_key2_string);
|
||||
let dht_key_secret2_string = String::from(&dht_key_secret2);
|
||||
assert_ne!(dht_key_secret2_string, dht_key_secret_string);
|
||||
assert_ne!(dht_key_secret2_string, dht_key2_string);
|
||||
|
||||
// Assert they convert back correctly
|
||||
let dht_key_back = PublicKey::try_from(dht_key_string.as_str()).unwrap();
|
||||
let dht_key_back2 = PublicKey::try_from(dht_key_string2.as_str()).unwrap();
|
||||
assert_eq!(dht_key_back, dht_key_back2);
|
||||
assert_eq!(dht_key_back, dht_key);
|
||||
assert_eq!(dht_key_back2, dht_key);
|
||||
|
||||
let dht_key_secret_back = SecretKey::try_from(dht_key_secret_string.as_str()).unwrap();
|
||||
assert_eq!(dht_key_secret_back, dht_key_secret);
|
||||
|
||||
let dht_key2_back = PublicKey::try_from(dht_key2_string.as_str()).unwrap();
|
||||
let dht_key2_back2 = PublicKey::try_from(dht_key2_string2.as_str()).unwrap();
|
||||
assert_eq!(dht_key2_back, dht_key2_back2);
|
||||
assert_eq!(dht_key2_back, dht_key2);
|
||||
assert_eq!(dht_key2_back2, dht_key2);
|
||||
|
||||
let dht_key_secret2_back = SecretKey::try_from(dht_key_secret2_string.as_str()).unwrap();
|
||||
assert_eq!(dht_key_secret2_back, dht_key_secret2);
|
||||
|
||||
// Assert string roundtrip
|
||||
assert_eq!(String::from(&dht_key2_back), dht_key2_string);
|
||||
// These conversions should fail
|
||||
assert!(PublicKey::try_from("whatever").is_err());
|
||||
assert!(SecretKey::try_from("whatever").is_err());
|
||||
assert!(PublicKey::try_from("").is_err());
|
||||
assert!(SecretKey::try_from("").is_err());
|
||||
assert!(PublicKey::try_from(" ").is_err());
|
||||
assert!(SecretKey::try_from(" ").is_err());
|
||||
assert!(PublicKey::try_from(
|
||||
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
|
||||
)
|
||||
.is_err());
|
||||
assert!(SecretKey::try_from(
|
||||
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
pub async fn test_encode_decode(vcrypto: CryptoSystemVersion) {
|
||||
let dht_key = PublicKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
|
||||
let dht_key_secret =
|
||||
SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap();
|
||||
let dht_key_b = PublicKey::new(EMPTY_KEY);
|
||||
let dht_key_secret_b = SecretKey::new(EMPTY_KEY_SECRET);
|
||||
assert_eq!(dht_key, dht_key_b);
|
||||
assert_eq!(dht_key_secret, dht_key_secret_b);
|
||||
|
||||
let (dht_key2, dht_key_secret2) = vcrypto.generate_keypair().into_split();
|
||||
|
||||
let e1 = dht_key.encode();
|
||||
trace!("e1: {:?}", e1);
|
||||
assert_eq!(e1, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned());
|
||||
let e1s = dht_key_secret.encode();
|
||||
trace!("e1s: {:?}", e1s);
|
||||
let e2 = dht_key2.encode();
|
||||
trace!("e2: {:?}", e2);
|
||||
let e2s = dht_key_secret2.encode();
|
||||
trace!("e2s: {:?}", e2s);
|
||||
|
||||
let d1 = PublicKey::try_decode(e1.as_str()).unwrap();
|
||||
trace!("d1: {:?}", d1);
|
||||
assert_eq!(dht_key, d1);
|
||||
|
||||
let d1s = SecretKey::try_decode(e1s.as_str()).unwrap();
|
||||
trace!("d1s: {:?}", d1s);
|
||||
assert_eq!(dht_key_secret, d1s);
|
||||
|
||||
let d2 = PublicKey::try_decode(e2.as_str()).unwrap();
|
||||
trace!("d2: {:?}", d2);
|
||||
assert_eq!(dht_key2, d2);
|
||||
|
||||
let d2s = SecretKey::try_decode(e2s.as_str()).unwrap();
|
||||
trace!("d2s: {:?}", d2s);
|
||||
assert_eq!(dht_key_secret2, d2s);
|
||||
|
||||
// Failures
|
||||
let f1 = SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
assert!(f1.is_err());
|
||||
let f2 = SecretKey::try_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&");
|
||||
assert!(f2.is_err());
|
||||
}
|
||||
|
||||
async fn test_hash(vcrypto: CryptoSystemVersion) {
|
||||
let mut s = BTreeSet::<PublicKey>::new();
|
||||
|
||||
let k1 = vcrypto.generate_hash("abc".as_bytes());
|
||||
let k2 = vcrypto.generate_hash("abcd".as_bytes());
|
||||
let k3 = vcrypto.generate_hash("".as_bytes());
|
||||
let k4 = vcrypto.generate_hash(" ".as_bytes());
|
||||
let k5 = vcrypto.generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let k6 = vcrypto.generate_hash(CHEEZBURGER.as_bytes());
|
||||
|
||||
s.insert(k1);
|
||||
s.insert(k2);
|
||||
s.insert(k3);
|
||||
s.insert(k4);
|
||||
s.insert(k5);
|
||||
s.insert(k6);
|
||||
assert_eq!(s.len(), 6);
|
||||
|
||||
let v1 = vcrypto.generate_hash("abc".as_bytes());
|
||||
let v2 = vcrypto.generate_hash("abcd".as_bytes());
|
||||
let v3 = vcrypto.generate_hash("".as_bytes());
|
||||
let v4 = vcrypto.generate_hash(" ".as_bytes());
|
||||
let v5 = vcrypto.generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let v6 = vcrypto.generate_hash(CHEEZBURGER.as_bytes());
|
||||
|
||||
assert_eq!(k1, v1);
|
||||
assert_eq!(k2, v2);
|
||||
assert_eq!(k3, v3);
|
||||
assert_eq!(k4, v4);
|
||||
assert_eq!(k5, v5);
|
||||
assert_eq!(k6, v6);
|
||||
|
||||
vcrypto.validate_hash("abc".as_bytes(), &v1);
|
||||
vcrypto.validate_hash("abcd".as_bytes(), &v2);
|
||||
vcrypto.validate_hash("".as_bytes(), &v3);
|
||||
vcrypto.validate_hash(" ".as_bytes(), &v4);
|
||||
vcrypto.validate_hash(LOREM_IPSUM.as_bytes(), &v5);
|
||||
vcrypto.validate_hash(CHEEZBURGER.as_bytes(), &v6);
|
||||
}
|
||||
|
||||
async fn test_operations(vcrypto: CryptoSystemVersion) {
|
||||
let k1 = vcrypto.generate_hash(LOREM_IPSUM.as_bytes());
|
||||
let k2 = vcrypto.generate_hash(CHEEZBURGER.as_bytes());
|
||||
let k3 = vcrypto.generate_hash("abc".as_bytes());
|
||||
|
||||
// Get distance
|
||||
let d1 = vcrypto.distance(&k1, &k2);
|
||||
let d2 = vcrypto.distance(&k2, &k1);
|
||||
let d3 = vcrypto.distance(&k1, &k3);
|
||||
let d4 = vcrypto.distance(&k2, &k3);
|
||||
|
||||
trace!("d1={:?}", d1);
|
||||
trace!("d2={:?}", d2);
|
||||
trace!("d3={:?}", d3);
|
||||
trace!("d4={:?}", d4);
|
||||
|
||||
// Verify commutativity
|
||||
assert_eq!(d1, d2);
|
||||
assert!(d1 <= d2);
|
||||
assert!(d1 >= d2);
|
||||
assert!(d1 >= d2);
|
||||
assert!(d1 <= d2);
|
||||
assert_eq!(d2, d1);
|
||||
assert!(d2 <= d1);
|
||||
assert!(d2 >= d1);
|
||||
assert!(d2 >= d1);
|
||||
assert!(d2 <= d1);
|
||||
|
||||
// Verify nibbles
|
||||
assert_eq!(d1.nibble(0), 0x9u8);
|
||||
assert_eq!(d1.nibble(1), 0x4u8);
|
||||
assert_eq!(d1.nibble(2), 0x3u8);
|
||||
assert_eq!(d1.nibble(3), 0x6u8);
|
||||
assert_eq!(d1.nibble(63), 0x6u8);
|
||||
|
||||
assert_eq!(d1.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
assert_eq!(d2.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
assert_eq!(d3.first_nonzero_nibble(), Some((1, 0x4u8)));
|
||||
assert_eq!(d4.first_nonzero_nibble(), Some((0, 0x9u8)));
|
||||
|
||||
// Verify bits
|
||||
assert_eq!(d1.bit(0), true);
|
||||
assert_eq!(d1.bit(1), false);
|
||||
assert_eq!(d1.bit(7), false);
|
||||
assert_eq!(d1.bit(8), false);
|
||||
assert_eq!(d1.bit(14), true);
|
||||
assert_eq!(d1.bit(15), false);
|
||||
assert_eq!(d1.bit(254), true);
|
||||
assert_eq!(d1.bit(255), false);
|
||||
|
||||
assert_eq!(d1.first_nonzero_bit(), Some(0));
|
||||
assert_eq!(d2.first_nonzero_bit(), Some(0));
|
||||
assert_eq!(d3.first_nonzero_bit(), Some(5));
|
||||
assert_eq!(d4.first_nonzero_bit(), Some(0));
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
let api = crypto_tests_startup().await;
|
||||
let crypto = api.crypto().unwrap();
|
||||
|
||||
// Test versions
|
||||
for v in VALID_CRYPTO_KINDS {
|
||||
let vcrypto = crypto.get(v).unwrap();
|
||||
|
||||
test_generate_secret(vcrypto.clone()).await;
|
||||
test_sign_and_verify(vcrypto.clone()).await;
|
||||
test_key_conversions(vcrypto.clone()).await;
|
||||
test_encode_decode(vcrypto.clone()).await;
|
||||
test_hash(vcrypto.clone()).await;
|
||||
test_operations(vcrypto).await;
|
||||
}
|
||||
|
||||
crypto_tests_shutdown(api.clone()).await;
|
||||
assert!(api.is_shutdown());
|
||||
}
|
184
veilid-core/src/crypto/types/crypto_typed.rs
Normal file
184
veilid-core/src/crypto/types/crypto_typed.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes, Hash, PartialEq, Eq))]
|
||||
pub struct CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
pub kind: CryptoKind,
|
||||
pub value: K,
|
||||
}
|
||||
|
||||
impl<K> CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
pub fn new(kind: CryptoKind, value: K) -> Self {
|
||||
Self { kind, value }
|
||||
}
|
||||
}
|
||||
impl<K> PartialOrd for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> Ord for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
let x = compare_crypto_kind(&self.kind, &other.kind);
|
||||
if x != cmp::Ordering::Equal {
|
||||
return x;
|
||||
}
|
||||
self.value.cmp(&other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> fmt::Display for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}:{}", self.kind, self.value)
|
||||
}
|
||||
}
|
||||
impl<K> FromStr for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let b = s.as_bytes();
|
||||
if b.len() != (5 + K::encoded_len()) || b[4..5] != b":"[..] {
|
||||
apibail_parse_error!("invalid typed key", s);
|
||||
}
|
||||
let kind: CryptoKind = b[0..4].try_into().expect("should not fail to convert");
|
||||
let value = K::try_decode_bytes(&b[5..])?;
|
||||
Ok(Self { kind, value })
|
||||
}
|
||||
}
|
||||
impl<'de, K> Deserialize<'de> for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = <String as Deserialize>::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
impl<K> Serialize for CryptoTyped<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ PartialOrd
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
303
veilid-core/src/crypto/types/crypto_typed_set.rs
Normal file
303
veilid-core/src/crypto/types/crypto_typed_set.rs
Normal file
@ -0,0 +1,303 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
Default,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes, Hash, PartialEq, Eq))]
|
||||
#[serde(from = "Vec<CryptoTyped<K>>", into = "Vec<CryptoTyped<K>>")]
|
||||
pub struct CryptoTypedSet<K = PublicKey>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
<Vec<CryptoTyped<K>> as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
items: Vec<CryptoTyped<K>>,
|
||||
}
|
||||
|
||||
impl<K> CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self { items: Vec::new() }
|
||||
}
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self {
|
||||
items: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
pub fn kinds(&self) -> Vec<CryptoKind> {
|
||||
let mut out = Vec::new();
|
||||
for tk in &self.items {
|
||||
out.push(tk.kind);
|
||||
}
|
||||
out.sort_by(compare_crypto_kind);
|
||||
out
|
||||
}
|
||||
pub fn keys(&self) -> Vec<K> {
|
||||
let mut out = Vec::new();
|
||||
for tk in &self.items {
|
||||
out.push(tk.value);
|
||||
}
|
||||
out
|
||||
}
|
||||
pub fn get(&self, kind: CryptoKind) -> Option<CryptoTyped<K>> {
|
||||
self.items.iter().find(|x| x.kind == kind).copied()
|
||||
}
|
||||
pub fn add(&mut self, typed_key: CryptoTyped<K>) {
|
||||
for x in &mut self.items {
|
||||
if x.kind == typed_key.kind {
|
||||
*x = typed_key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.items.push(typed_key);
|
||||
self.items.sort()
|
||||
}
|
||||
pub fn add_all(&mut self, typed_keys: &[CryptoTyped<K>]) {
|
||||
'outer: for typed_key in typed_keys {
|
||||
for x in &mut self.items {
|
||||
if x.kind == typed_key.kind {
|
||||
*x = *typed_key;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
self.items.push(*typed_key);
|
||||
}
|
||||
self.items.sort()
|
||||
}
|
||||
pub fn remove(&mut self, kind: CryptoKind) {
|
||||
if let Some(idx) = self.items.iter().position(|x| x.kind == kind) {
|
||||
self.items.remove(idx);
|
||||
}
|
||||
}
|
||||
pub fn remove_all(&mut self, kinds: &[CryptoKind]) {
|
||||
for k in kinds {
|
||||
self.remove(*k);
|
||||
}
|
||||
}
|
||||
/// Return preferred typed key of our supported crypto kinds
|
||||
pub fn best(&self) -> Option<CryptoTyped<K>> {
|
||||
match self.items.first().copied() {
|
||||
None => None,
|
||||
Some(k) => {
|
||||
if !VALID_CRYPTO_KINDS.contains(&k.kind) {
|
||||
None
|
||||
} else {
|
||||
Some(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
pub fn iter(&self) -> core::slice::Iter<'_, CryptoTyped<K>> {
|
||||
self.items.iter()
|
||||
}
|
||||
pub fn contains(&self, typed_key: &CryptoTyped<K>) -> bool {
|
||||
self.items.contains(typed_key)
|
||||
}
|
||||
pub fn contains_any(&self, typed_keys: &[CryptoTyped<K>]) -> bool {
|
||||
for typed_key in typed_keys {
|
||||
if self.items.contains(typed_key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn contains_key(&self, key: &K) -> bool {
|
||||
for tk in &self.items {
|
||||
if tk.value == *key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> core::ops::Deref for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
type Target = [CryptoTyped<K>];
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &[CryptoTyped<K>] {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> fmt::Display for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "[")?;
|
||||
let mut first = true;
|
||||
for x in &self.items {
|
||||
if !first {
|
||||
write!(f, ",")?;
|
||||
first = false;
|
||||
}
|
||||
write!(f, "{}", x)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
impl<K> FromStr for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut items = Vec::new();
|
||||
if s.len() < 2 {
|
||||
apibail_parse_error!("invalid length", s);
|
||||
}
|
||||
if &s[0..1] != "[" || &s[(s.len() - 1)..] != "]" {
|
||||
apibail_parse_error!("invalid format", s);
|
||||
}
|
||||
for x in s[1..s.len() - 1].split(",") {
|
||||
let tk = CryptoTyped::<K>::from_str(x.trim())?;
|
||||
items.push(tk);
|
||||
}
|
||||
|
||||
Ok(Self { items })
|
||||
}
|
||||
}
|
||||
impl<K> From<CryptoTyped<K>> for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn from(x: CryptoTyped<K>) -> Self {
|
||||
let mut tks = CryptoTypedSet::<K>::with_capacity(1);
|
||||
tks.add(x);
|
||||
tks
|
||||
}
|
||||
}
|
||||
impl<K> From<Vec<CryptoTyped<K>>> for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn from(x: Vec<CryptoTyped<K>>) -> Self {
|
||||
let mut tks = CryptoTypedSet::<K>::with_capacity(x.len());
|
||||
tks.add_all(&x);
|
||||
tks
|
||||
}
|
||||
}
|
||||
impl<K> Into<Vec<CryptoTyped<K>>> for CryptoTypedSet<K>
|
||||
where
|
||||
K: Clone
|
||||
+ Copy
|
||||
+ fmt::Debug
|
||||
+ fmt::Display
|
||||
+ FromStr
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ RkyvArchive
|
||||
+ Encodable,
|
||||
<K as RkyvArchive>::Archived: Hash + PartialEq + Eq,
|
||||
{
|
||||
fn into(self) -> Vec<CryptoTyped<K>> {
|
||||
self.items
|
||||
}
|
||||
}
|
91
veilid-core/src/crypto/types/keypair.rs
Normal file
91
veilid-core/src/crypto/types/keypair.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes, Hash, PartialEq, Eq))]
|
||||
pub struct KeyPair {
|
||||
pub key: PublicKey,
|
||||
pub secret: SecretKey,
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
pub fn new(key: PublicKey, secret: SecretKey) -> Self {
|
||||
Self { key, secret }
|
||||
}
|
||||
pub fn split(&self) -> (PublicKey, SecretKey) {
|
||||
(self.key, self.secret)
|
||||
}
|
||||
pub fn into_split(self) -> (PublicKey, SecretKey) {
|
||||
(self.key, self.secret)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for KeyPair {
|
||||
fn encode(&self) -> String {
|
||||
format!("{}:{}", self.key.encode(), self.secret.encode())
|
||||
}
|
||||
fn encoded_len() -> usize {
|
||||
PublicKey::encoded_len() + 1 + SecretKey::encoded_len()
|
||||
}
|
||||
fn try_decode_bytes(b: &[u8]) -> Result<Self, VeilidAPIError> {
|
||||
if b.len() != Self::encoded_len() {
|
||||
apibail_parse_error!("input has wrong encoded length", format!("len={}", b.len()));
|
||||
}
|
||||
let key = PublicKey::try_decode_bytes(&b[0..PublicKey::encoded_len()])?;
|
||||
let secret = SecretKey::try_decode_bytes(&b[(PublicKey::encoded_len() + 1)..])?;
|
||||
Ok(KeyPair { key, secret })
|
||||
}
|
||||
}
|
||||
impl fmt::Display for KeyPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for KeyPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, concat!(stringify!($name), "("))?;
|
||||
write!(f, "{}", self.encode())?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KeyPair> for String {
|
||||
fn from(value: &KeyPair) -> Self {
|
||||
value.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for KeyPair {
|
||||
type Err = VeilidAPIError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
KeyPair::try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for KeyPair {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
KeyPair::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for KeyPair {
|
||||
type Error = VeilidAPIError;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Self::try_decode(value)
|
||||
}
|
||||
}
|
59
veilid-core/src/crypto/types/mod.rs
Normal file
59
veilid-core/src/crypto/types/mod.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use super::*;
|
||||
|
||||
use core::cmp::{Eq, Ord, PartialEq, PartialOrd};
|
||||
use core::convert::TryInto;
|
||||
use core::fmt;
|
||||
use core::hash::Hash;
|
||||
|
||||
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
|
||||
/// Cryptography version fourcc code
|
||||
pub type CryptoKind = FourCC;
|
||||
|
||||
/// Sort best crypto kinds first
|
||||
/// Better crypto kinds are 'less', ordered toward the front of a list
|
||||
pub fn compare_crypto_kind(a: &CryptoKind, b: &CryptoKind) -> cmp::Ordering {
|
||||
let a_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == a);
|
||||
let b_idx = VALID_CRYPTO_KINDS.iter().position(|k| k == b);
|
||||
if let Some(a_idx) = a_idx {
|
||||
if let Some(b_idx) = b_idx {
|
||||
// Both are valid, prefer better crypto kind
|
||||
a_idx.cmp(&b_idx)
|
||||
} else {
|
||||
// A is valid, B is not
|
||||
cmp::Ordering::Less
|
||||
}
|
||||
} else if b_idx.is_some() {
|
||||
// B is valid, A is not
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
// Both are invalid, so use lex comparison
|
||||
a.cmp(b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersection of crypto kind vectors
|
||||
pub fn common_crypto_kinds(a: &[CryptoKind], b: &[CryptoKind]) -> Vec<CryptoKind> {
|
||||
let mut out = Vec::new();
|
||||
for ack in a {
|
||||
if b.contains(ack) {
|
||||
out.push(*ack);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
mod crypto_typed;
|
||||
mod crypto_typed_set;
|
||||
mod keypair;
|
||||
|
||||
pub use crypto_typed::*;
|
||||
pub use crypto_typed_set::*;
|
||||
pub use keypair::*;
|
||||
|
||||
pub type TypedKey = CryptoTyped<PublicKey>;
|
||||
pub type TypedSecret = CryptoTyped<SecretKey>;
|
||||
pub type TypedKeyPair = CryptoTyped<KeyPair>;
|
||||
pub type TypedSignature = CryptoTyped<Signature>;
|
||||
pub type TypedKeySet = CryptoTypedSet<PublicKey>;
|
||||
pub type TypedSecretSet = CryptoTypedSet<SecretKey>;
|
70
veilid-core/src/crypto/vld0/blake3digest512.rs
Normal file
70
veilid-core/src/crypto/vld0/blake3digest512.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use digest::generic_array::typenum::U64;
|
||||
use digest::{Digest, Output};
|
||||
use generic_array::GenericArray;
|
||||
|
||||
pub struct Blake3Digest512 {
|
||||
dig: blake3::Hasher,
|
||||
}
|
||||
|
||||
impl Digest for Blake3Digest512 {
|
||||
type OutputSize = U64;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
dig: blake3::Hasher::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||
self.dig.update(data.as_ref());
|
||||
}
|
||||
|
||||
fn chain(mut self, data: impl AsRef<[u8]>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.update(data);
|
||||
self
|
||||
}
|
||||
|
||||
fn finalize(self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn finalize_reset(&mut self) -> Output<Self> {
|
||||
let mut b = [0u8; 64];
|
||||
self.dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
self.reset();
|
||||
out
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.dig.reset();
|
||||
}
|
||||
|
||||
fn output_size() -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn digest(data: &[u8]) -> Output<Self> {
|
||||
let mut dig = blake3::Hasher::new();
|
||||
dig.update(data);
|
||||
let mut b = [0u8; 64];
|
||||
dig.finalize_xof().fill(&mut b);
|
||||
let mut out = GenericArray::<u8, U64>::default();
|
||||
for n in 0..64 {
|
||||
out[n] = b[n];
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
300
veilid-core/src/crypto/vld0/mod.rs
Normal file
300
veilid-core/src/crypto/vld0/mod.rs
Normal file
@ -0,0 +1,300 @@
|
||||
pub mod blake3digest512;
|
||||
pub use blake3digest512::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
use chacha20::cipher::{KeyIvInit, StreamCipher};
|
||||
use chacha20::XChaCha20;
|
||||
use chacha20poly1305 as ch;
|
||||
use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
||||
use core::convert::TryInto;
|
||||
use curve25519_dalek as cd;
|
||||
use digest::Digest;
|
||||
use ed25519_dalek as ed;
|
||||
use x25519_dalek as xd;
|
||||
|
||||
const AEAD_OVERHEAD: usize = 16;
|
||||
pub const CRYPTO_KIND_VLD0: CryptoKind = FourCC([b'V', b'L', b'D', b'0']);
|
||||
|
||||
fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> Result<xd::PublicKey, VeilidAPIError> {
|
||||
let bytes = key.to_bytes();
|
||||
let compressed = cd::edwards::CompressedEdwardsY(bytes);
|
||||
let point = compressed
|
||||
.decompress()
|
||||
.ok_or_else(|| VeilidAPIError::internal("ed25519_to_x25519_pk failed"))?;
|
||||
let mp = point.to_montgomery();
|
||||
Ok(xd::PublicKey::from(mp.to_bytes()))
|
||||
}
|
||||
fn ed25519_to_x25519_sk(key: &ed::SecretKey) -> Result<xd::StaticSecret, VeilidAPIError> {
|
||||
let exp = ed::ExpandedSecretKey::from(key);
|
||||
let bytes: [u8; ed::EXPANDED_SECRET_KEY_LENGTH] = exp.to_bytes();
|
||||
let lowbytes: [u8; 32] = bytes[0..32].try_into().map_err(VeilidAPIError::internal)?;
|
||||
Ok(xd::StaticSecret::from(lowbytes))
|
||||
}
|
||||
|
||||
pub fn vld0_generate_keypair() -> KeyPair {
|
||||
let mut csprng = VeilidRng {};
|
||||
let keypair = ed::Keypair::generate(&mut csprng);
|
||||
let dht_key = PublicKey::new(keypair.public.to_bytes());
|
||||
let dht_key_secret = SecretKey::new(keypair.secret.to_bytes());
|
||||
|
||||
KeyPair::new(dht_key, dht_key_secret)
|
||||
}
|
||||
|
||||
/// V0 CryptoSystem
|
||||
#[derive(Clone)]
|
||||
pub struct CryptoSystemVLD0 {
|
||||
crypto: Crypto,
|
||||
}
|
||||
|
||||
impl CryptoSystemVLD0 {
|
||||
pub fn new(crypto: Crypto) -> Self {
|
||||
Self { crypto }
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptoSystem for CryptoSystemVLD0 {
|
||||
// Accessors
|
||||
fn kind(&self) -> CryptoKind {
|
||||
CRYPTO_KIND_VLD0
|
||||
}
|
||||
|
||||
fn crypto(&self) -> Crypto {
|
||||
self.crypto.clone()
|
||||
}
|
||||
|
||||
// Cached Operations
|
||||
fn cached_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError> {
|
||||
self.crypto
|
||||
.cached_dh_internal::<CryptoSystemVLD0>(self, key, secret)
|
||||
}
|
||||
|
||||
// Generation
|
||||
fn random_nonce(&self) -> Nonce {
|
||||
let mut nonce = [0u8; 24];
|
||||
random_bytes(&mut nonce).unwrap();
|
||||
Nonce::new(nonce)
|
||||
}
|
||||
fn random_shared_secret(&self) -> SharedSecret {
|
||||
let mut s = [0u8; 32];
|
||||
random_bytes(&mut s).unwrap();
|
||||
SharedSecret::new(s)
|
||||
}
|
||||
fn compute_dh(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
) -> Result<SharedSecret, VeilidAPIError> {
|
||||
let pk_ed = ed::PublicKey::from_bytes(&key.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let pk_xd = ed25519_to_x25519_pk(&pk_ed)?;
|
||||
let sk_ed = ed::SecretKey::from_bytes(&secret.bytes).map_err(VeilidAPIError::internal)?;
|
||||
let sk_xd = ed25519_to_x25519_sk(&sk_ed)?;
|
||||
Ok(SharedSecret::new(sk_xd.diffie_hellman(&pk_xd).to_bytes()))
|
||||
}
|
||||
fn generate_keypair(&self) -> KeyPair {
|
||||
vld0_generate_keypair()
|
||||
}
|
||||
fn generate_hash(&self, data: &[u8]) -> PublicKey {
|
||||
PublicKey::new(*blake3::hash(data).as_bytes())
|
||||
}
|
||||
fn generate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
) -> Result<PublicKey, VeilidAPIError> {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
|
||||
Ok(PublicKey::new(*hasher.finalize().as_bytes()))
|
||||
}
|
||||
|
||||
// Validation
|
||||
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool {
|
||||
let data = vec![0u8; 512];
|
||||
let sig = match self.sign(dht_key, dht_key_secret, &data) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
self.verify(dht_key, &data, &sig).is_ok()
|
||||
}
|
||||
fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool {
|
||||
let bytes = *blake3::hash(data).as_bytes();
|
||||
|
||||
bytes == dht_key.bytes
|
||||
}
|
||||
fn validate_hash_reader(
|
||||
&self,
|
||||
reader: &mut dyn std::io::Read,
|
||||
dht_key: &PublicKey,
|
||||
) -> Result<bool, VeilidAPIError> {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
std::io::copy(reader, &mut hasher).map_err(VeilidAPIError::generic)?;
|
||||
let bytes = *hasher.finalize().as_bytes();
|
||||
Ok(bytes == dht_key.bytes)
|
||||
}
|
||||
// Distance Metric
|
||||
fn distance(&self, key1: &PublicKey, key2: &PublicKey) -> PublicKeyDistance {
|
||||
let mut bytes = [0u8; PUBLIC_KEY_LENGTH];
|
||||
|
||||
for (n, byte) in bytes.iter_mut().enumerate() {
|
||||
*byte = key1.bytes[n] ^ key2.bytes[n];
|
||||
}
|
||||
|
||||
PublicKeyDistance::new(bytes)
|
||||
}
|
||||
|
||||
// Authentication
|
||||
fn sign(
|
||||
&self,
|
||||
dht_key: &PublicKey,
|
||||
dht_key_secret: &SecretKey,
|
||||
data: &[u8],
|
||||
) -> Result<Signature, VeilidAPIError> {
|
||||
let mut kpb: [u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] =
|
||||
[0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH];
|
||||
|
||||
kpb[..SECRET_KEY_LENGTH].copy_from_slice(&dht_key_secret.bytes);
|
||||
kpb[SECRET_KEY_LENGTH..].copy_from_slice(&dht_key.bytes);
|
||||
let keypair = ed::Keypair::from_bytes(&kpb)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Keypair is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
let sig = keypair
|
||||
.sign_prehashed(dig, None)
|
||||
.map_err(VeilidAPIError::internal)?;
|
||||
|
||||
let dht_sig = Signature::new(sig.to_bytes());
|
||||
Ok(dht_sig)
|
||||
}
|
||||
fn verify(
|
||||
&self,
|
||||
dht_key: &PublicKey,
|
||||
data: &[u8],
|
||||
signature: &Signature,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let pk = ed::PublicKey::from_bytes(&dht_key.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
|
||||
let sig = ed::Signature::from_bytes(&signature.bytes)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Signature is invalid", e))?;
|
||||
|
||||
let mut dig = Blake3Digest512::new();
|
||||
dig.update(data);
|
||||
|
||||
pk.verify_prehashed(dig, None, &sig)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// AEAD Encrypt/Decrypt
|
||||
fn aead_overhead(&self) -> usize {
|
||||
AEAD_OVERHEAD
|
||||
}
|
||||
fn decrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(shared_secret.bytes);
|
||||
let xnonce = ch::XNonce::from(nonce.bytes);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
aead.decrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
fn decrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
self.decrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn encrypt_in_place_aead(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<(), VeilidAPIError> {
|
||||
let key = ch::Key::from(shared_secret.bytes);
|
||||
let xnonce = ch::XNonce::from(nonce.bytes);
|
||||
let aead = ch::XChaCha20Poly1305::new(&key);
|
||||
|
||||
aead.encrypt_in_place(&xnonce, associated_data.unwrap_or(b""), body)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
fn encrypt_aead(
|
||||
&self,
|
||||
body: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
associated_data: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, VeilidAPIError> {
|
||||
let mut out = body.to_vec();
|
||||
self.encrypt_in_place_aead(&mut out, nonce, shared_secret, associated_data)
|
||||
.map_err(map_to_string)
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
// NoAuth Encrypt/Decrypt
|
||||
fn crypt_in_place_no_auth(
|
||||
&self,
|
||||
body: &mut Vec<u8>,
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) {
|
||||
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
|
||||
cipher.apply_keystream(body);
|
||||
}
|
||||
|
||||
fn crypt_b2b_no_auth(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
out_buf: &mut [u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) {
|
||||
let mut cipher = XChaCha20::new(&shared_secret.bytes.into(), &nonce.bytes.into());
|
||||
cipher.apply_keystream_b2b(in_buf, out_buf).unwrap();
|
||||
}
|
||||
|
||||
fn crypt_no_auth_aligned_8(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8> {
|
||||
let mut out_buf = unsafe { aligned_8_u8_vec_uninit(in_buf.len()) };
|
||||
self.crypt_b2b_no_auth(in_buf, &mut out_buf, nonce, shared_secret);
|
||||
out_buf
|
||||
}
|
||||
|
||||
fn crypt_no_auth_unaligned(
|
||||
&self,
|
||||
in_buf: &[u8],
|
||||
nonce: &Nonce,
|
||||
shared_secret: &SharedSecret,
|
||||
) -> Vec<u8> {
|
||||
let mut out_buf = unsafe { unaligned_u8_vec_uninit(in_buf.len()) };
|
||||
self.crypt_b2b_no_auth(in_buf, &mut out_buf, nonce, shared_secret);
|
||||
out_buf
|
||||
}
|
||||
}
|
@ -113,28 +113,35 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value), ret, err)]
|
||||
pub async fn save_user_secret_string(&self, key: &str, value: &str) -> EyreResult<bool> {
|
||||
pub async fn save_user_secret_string<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> EyreResult<bool> {
|
||||
let inner = self.inner.lock();
|
||||
inner
|
||||
.keyring_manager
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre!("Protected store not initialized"))?
|
||||
.with_keyring(&self.service_name(), key, |kr| {
|
||||
.with_keyring(&self.service_name(), key.as_ref(), |kr| {
|
||||
let existed = kr.get_value().is_ok();
|
||||
kr.set_value(value)?;
|
||||
kr.set_value(value.as_ref())?;
|
||||
Ok(existed)
|
||||
})
|
||||
.wrap_err("failed to save user secret")
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub async fn load_user_secret_string(&self, key: &str) -> EyreResult<Option<String>> {
|
||||
pub async fn load_user_secret_string<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> EyreResult<Option<String>> {
|
||||
let inner = self.inner.lock();
|
||||
match inner
|
||||
.keyring_manager
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre!("Protected store not initialized"))?
|
||||
.with_keyring(&self.service_name(), key, |kr| kr.get_value())
|
||||
.with_keyring(&self.service_name(), key.as_ref(), |kr| kr.get_value())
|
||||
{
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(KeyringError::NoPasswordFound) => Ok(None),
|
||||
@ -143,17 +150,19 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value))]
|
||||
pub async fn save_user_secret_rkyv<T>(&self, key: &str, value: &T) -> EyreResult<bool>
|
||||
pub async fn save_user_secret_rkyv<K, T>(&self, key: K, value: &T) -> EyreResult<bool>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: RkyvSerialize<rkyv::ser::serializers::AllocSerializer<1024>>,
|
||||
{
|
||||
let v = to_rkyv(value)?;
|
||||
self.save_user_secret(&key, &v).await
|
||||
self.save_user_secret(key, &v).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value))]
|
||||
pub async fn save_user_secret_json<T>(&self, key: &str, value: &T) -> EyreResult<bool>
|
||||
pub async fn save_user_secret_json<K, T>(&self, key: K, value: &T) -> EyreResult<bool>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let v = serde_json::to_vec(value)?;
|
||||
@ -161,8 +170,9 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
pub async fn load_user_secret_rkyv<T>(&self, key: &str) -> EyreResult<Option<T>>
|
||||
pub async fn load_user_secret_rkyv<K, T>(&self, key: K) -> EyreResult<Option<T>>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: RkyvArchive,
|
||||
<T as RkyvArchive>::Archived:
|
||||
for<'t> bytecheck::CheckBytes<rkyv::validation::validators::DefaultValidator<'t>>,
|
||||
@ -182,8 +192,9 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
pub async fn load_user_secret_json<T>(&self, key: &str) -> EyreResult<Option<T>>
|
||||
pub async fn load_user_secret_json<K, T>(&self, key: K) -> EyreResult<Option<T>>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let out = self.load_user_secret(key).await?;
|
||||
@ -199,7 +210,11 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value), ret, err)]
|
||||
pub async fn save_user_secret(&self, key: &str, value: &[u8]) -> EyreResult<bool> {
|
||||
pub async fn save_user_secret<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
value: &[u8],
|
||||
) -> EyreResult<bool> {
|
||||
let mut s = BASE64URL_NOPAD.encode(value);
|
||||
s.push('!');
|
||||
|
||||
@ -207,7 +222,10 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub async fn load_user_secret(&self, key: &str) -> EyreResult<Option<Vec<u8>>> {
|
||||
pub async fn load_user_secret<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> EyreResult<Option<Vec<u8>>> {
|
||||
let mut s = match self.load_user_secret_string(key).await? {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
@ -238,13 +256,13 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn remove_user_secret(&self, key: &str) -> EyreResult<bool> {
|
||||
pub async fn remove_user_secret<K: AsRef<str> + fmt::Debug>(&self, key: K) -> EyreResult<bool> {
|
||||
let inner = self.inner.lock();
|
||||
match inner
|
||||
.keyring_manager
|
||||
.as_ref()
|
||||
.ok_or_else(|| eyre!("Protected store not initialized"))?
|
||||
.with_keyring(&self.service_name(), key, |kr| kr.delete_value())
|
||||
.with_keyring(&self.service_name(), key.as_ref(), |kr| kr.delete_value())
|
||||
{
|
||||
Ok(_) => Ok(true),
|
||||
Err(KeyringError::NoPasswordFound) => Ok(false),
|
||||
|
@ -50,7 +50,11 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
//#[instrument(level = "trace", skip(self, value), ret, err)]
|
||||
pub async fn save_user_secret_string(&self, key: &str, value: &str) -> EyreResult<bool> {
|
||||
pub async fn save_user_secret_string<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> EyreResult<bool> {
|
||||
if is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
@ -70,7 +74,7 @@ impl ProtectedStore {
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = self.browser_key_name(key);
|
||||
let vkey = self.browser_key_name(key.as_ref());
|
||||
|
||||
let prev = match ls
|
||||
.get_item(&vkey)
|
||||
@ -81,7 +85,7 @@ impl ProtectedStore {
|
||||
None => false,
|
||||
};
|
||||
|
||||
ls.set_item(&vkey, value)
|
||||
ls.set_item(&vkey, value.as_ref())
|
||||
.map_err(map_jsvalue_error)
|
||||
.wrap_err("exception_thrown")?;
|
||||
|
||||
@ -92,7 +96,10 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub async fn load_user_secret_string(&self, key: &str) -> EyreResult<Option<String>> {
|
||||
pub async fn load_user_secret_string<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> EyreResult<Option<String>> {
|
||||
if is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
@ -112,7 +119,7 @@ impl ProtectedStore {
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = self.browser_key_name(key);
|
||||
let vkey = self.browser_key_name(key.as_ref());
|
||||
|
||||
ls.get_item(&vkey)
|
||||
.map_err(map_jsvalue_error)
|
||||
@ -123,26 +130,29 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value))]
|
||||
pub async fn save_user_secret_rkyv<T>(&self, key: &str, value: &T) -> EyreResult<bool>
|
||||
pub async fn save_user_secret_rkyv<K, T>(&self, key: K, value: &T) -> EyreResult<bool>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: RkyvSerialize<rkyv::ser::serializers::AllocSerializer<1024>>,
|
||||
{
|
||||
let v = to_rkyv(value)?;
|
||||
self.save_user_secret(&key, &v).await
|
||||
self.save_user_secret(key, &v).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value))]
|
||||
pub async fn save_user_secret_json<T>(&self, key: &str, value: &T) -> EyreResult<bool>
|
||||
pub async fn save_user_secret_json<K, T>(&self, key: K, value: &T) -> EyreResult<bool>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let v = serde_json::to_vec(value)?;
|
||||
self.save_user_secret(&key, &v).await
|
||||
self.save_user_secret(key, &v).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
pub async fn load_user_secret_rkyv<T>(&self, key: &str) -> EyreResult<Option<T>>
|
||||
pub async fn load_user_secret_rkyv<K, T>(&self, key: K) -> EyreResult<Option<T>>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: RkyvArchive,
|
||||
<T as RkyvArchive>::Archived:
|
||||
for<'t> bytecheck::CheckBytes<rkyv::validation::validators::DefaultValidator<'t>>,
|
||||
@ -162,8 +172,9 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
pub async fn load_user_secret_json<T>(&self, key: &str) -> EyreResult<Option<T>>
|
||||
pub async fn load_user_secret_json<K, T>(&self, key: K) -> EyreResult<Option<T>>
|
||||
where
|
||||
K: AsRef<str> + fmt::Debug,
|
||||
T: for<'de> serde::de::Deserialize<'de>,
|
||||
{
|
||||
let out = self.load_user_secret(key).await?;
|
||||
@ -179,7 +190,11 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, value), ret, err)]
|
||||
pub async fn save_user_secret(&self, key: &str, value: &[u8]) -> EyreResult<bool> {
|
||||
pub async fn save_user_secret<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
value: &[u8],
|
||||
) -> EyreResult<bool> {
|
||||
let mut s = BASE64URL_NOPAD.encode(value);
|
||||
s.push('!');
|
||||
|
||||
@ -187,7 +202,10 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub async fn load_user_secret(&self, key: &str) -> EyreResult<Option<Vec<u8>>> {
|
||||
pub async fn load_user_secret<K: AsRef<str> + fmt::Debug>(
|
||||
&self,
|
||||
key: K,
|
||||
) -> EyreResult<Option<Vec<u8>>> {
|
||||
let mut s = match self.load_user_secret_string(key).await? {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
@ -218,7 +236,7 @@ impl ProtectedStore {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn remove_user_secret(&self, key: &str) -> EyreResult<bool> {
|
||||
pub async fn remove_user_secret<K: AsRef<str> + fmt::Debug>(&self, key: K) -> EyreResult<bool> {
|
||||
if is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
@ -238,7 +256,7 @@ impl ProtectedStore {
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = self.browser_key_name(key);
|
||||
let vkey = self.browser_key_name(key.as_ref());
|
||||
|
||||
match ls
|
||||
.get_item(&vkey)
|
||||
|
@ -35,6 +35,7 @@ mod veilid_layer_filter;
|
||||
|
||||
pub use self::api_tracing_layer::ApiTracingLayer;
|
||||
pub use self::core_context::{api_startup, api_startup_json, UpdateCallback};
|
||||
pub use self::crypto::vld0_generate_keypair;
|
||||
pub use self::veilid_api::*;
|
||||
pub use self::veilid_config::*;
|
||||
pub use self::veilid_layer_filter::*;
|
||||
|
@ -83,6 +83,7 @@ impl ConnectionTable {
|
||||
unord.push(v);
|
||||
}
|
||||
}
|
||||
inner.protocol_index_by_id.clear();
|
||||
inner.id_by_descriptor.clear();
|
||||
inner.ids_by_remote.clear();
|
||||
unord
|
||||
@ -136,7 +137,9 @@ impl ConnectionTable {
|
||||
};
|
||||
|
||||
// Add the connection to the table
|
||||
let res = inner.conn_by_id[protocol_index].insert(id, network_connection);
|
||||
let res = inner.conn_by_id[protocol_index].insert(id, network_connection, |_k, _v| {
|
||||
// never lrus, unbounded
|
||||
});
|
||||
assert!(res.is_none());
|
||||
|
||||
// if we have reached the maximum number of connections per protocol type
|
||||
|
@ -120,7 +120,7 @@ pub(crate) enum NodeContactMethod {
|
||||
Direct(DialInfo),
|
||||
/// Request via signal the node connect back directly (relay, target)
|
||||
SignalReverse(NodeRef, NodeRef),
|
||||
/// Request via signal the node negotiate a hole punch (relay, target_node)
|
||||
/// Request via signal the node negotiate a hole punch (relay, target)
|
||||
SignalHolePunch(NodeRef, NodeRef),
|
||||
/// Must use an inbound relay to reach the node
|
||||
InboundRelay(NodeRef),
|
||||
@ -134,7 +134,7 @@ struct PublicAddressCheckCacheKey(ProtocolType, AddressType);
|
||||
// The mutable state of the network manager
|
||||
struct NetworkManagerInner {
|
||||
stats: NetworkManagerStats,
|
||||
client_whitelist: LruCache<DHTKey, ClientWhitelistEntry>,
|
||||
client_whitelist: LruCache<TypedKey, ClientWhitelistEntry>,
|
||||
public_address_check_cache:
|
||||
BTreeMap<PublicAddressCheckCacheKey, LruCache<IpAddr, SocketAddress>>,
|
||||
public_address_inconsistencies_table:
|
||||
@ -396,9 +396,11 @@ impl NetworkManager {
|
||||
debug!("finished network manager shutdown");
|
||||
}
|
||||
|
||||
pub fn update_client_whitelist(&self, client: DHTKey) {
|
||||
pub fn update_client_whitelist(&self, client: TypedKey) {
|
||||
let mut inner = self.inner.lock();
|
||||
match inner.client_whitelist.entry(client) {
|
||||
match inner.client_whitelist.entry(client, |_k,_v| {
|
||||
// do nothing on LRU evict
|
||||
}) {
|
||||
hashlink::lru_cache::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().last_seen_ts = get_aligned_timestamp()
|
||||
}
|
||||
@ -411,10 +413,12 @@ impl NetworkManager {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret)]
|
||||
pub fn check_client_whitelist(&self, client: DHTKey) -> bool {
|
||||
pub fn check_client_whitelist(&self, client: TypedKey) -> bool {
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
match inner.client_whitelist.entry(client) {
|
||||
match inner.client_whitelist.entry(client, |_k,_v| {
|
||||
// do nothing on LRU evict
|
||||
}) {
|
||||
hashlink::lru_cache::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().last_seen_ts = get_aligned_timestamp();
|
||||
true
|
||||
@ -519,10 +523,15 @@ impl NetworkManager {
|
||||
let routing_table = self.routing_table();
|
||||
|
||||
// Generate receipt and serialized form to return
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let receipt = Receipt::try_new(0, nonce, routing_table.node_id(), extra_data)?;
|
||||
let vcrypto = self.crypto().best();
|
||||
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let node_id = routing_table.node_id(vcrypto.kind());
|
||||
let node_id_secret = routing_table.node_id_secret_key(vcrypto.kind());
|
||||
|
||||
let receipt = Receipt::try_new(best_envelope_version(), node_id.kind, nonce, node_id.value, extra_data)?;
|
||||
let out = receipt
|
||||
.to_signed_data(&routing_table.node_id_secret())
|
||||
.to_signed_data(self.crypto(), &node_id_secret)
|
||||
.wrap_err("failed to generate signed receipt")?;
|
||||
|
||||
// Record the receipt for later
|
||||
@ -543,10 +552,15 @@ impl NetworkManager {
|
||||
let routing_table = self.routing_table();
|
||||
|
||||
// Generate receipt and serialized form to return
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let receipt = Receipt::try_new(0, nonce, routing_table.node_id(), extra_data)?;
|
||||
let vcrypto = self.crypto().best();
|
||||
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let node_id = routing_table.node_id(vcrypto.kind());
|
||||
let node_id_secret = routing_table.node_id_secret_key(vcrypto.kind());
|
||||
|
||||
let receipt = Receipt::try_new(best_envelope_version(), node_id.kind, nonce, node_id.value, extra_data)?;
|
||||
let out = receipt
|
||||
.to_signed_data(&routing_table.node_id_secret())
|
||||
.to_signed_data(self.crypto(), &node_id_secret)
|
||||
.wrap_err("failed to generate signed receipt")?;
|
||||
|
||||
// Record the receipt for later
|
||||
@ -566,7 +580,7 @@ impl NetworkManager {
|
||||
) -> NetworkResult<()> {
|
||||
let receipt_manager = self.receipt_manager();
|
||||
|
||||
let receipt = match Receipt::from_signed_data(receipt_data.as_ref()) {
|
||||
let receipt = match Receipt::from_signed_data(self.crypto(), receipt_data.as_ref()) {
|
||||
Err(e) => {
|
||||
return NetworkResult::invalid_message(e.to_string());
|
||||
}
|
||||
@ -587,7 +601,7 @@ impl NetworkManager {
|
||||
) -> NetworkResult<()> {
|
||||
let receipt_manager = self.receipt_manager();
|
||||
|
||||
let receipt = match Receipt::from_signed_data(receipt_data.as_ref()) {
|
||||
let receipt = match Receipt::from_signed_data(self.crypto(), receipt_data.as_ref()) {
|
||||
Err(e) => {
|
||||
return NetworkResult::invalid_message(e.to_string());
|
||||
}
|
||||
@ -607,7 +621,7 @@ impl NetworkManager {
|
||||
) -> NetworkResult<()> {
|
||||
let receipt_manager = self.receipt_manager();
|
||||
|
||||
let receipt = match Receipt::from_signed_data(receipt_data.as_ref()) {
|
||||
let receipt = match Receipt::from_signed_data(self.crypto(), receipt_data.as_ref()) {
|
||||
Err(e) => {
|
||||
return NetworkResult::invalid_message(e.to_string());
|
||||
}
|
||||
@ -624,11 +638,11 @@ impl NetworkManager {
|
||||
pub async fn handle_private_receipt<R: AsRef<[u8]>>(
|
||||
&self,
|
||||
receipt_data: R,
|
||||
private_route: DHTKey,
|
||||
private_route: PublicKey,
|
||||
) -> NetworkResult<()> {
|
||||
let receipt_manager = self.receipt_manager();
|
||||
|
||||
let receipt = match Receipt::from_signed_data(receipt_data.as_ref()) {
|
||||
let receipt = match Receipt::from_signed_data(self.crypto(), receipt_data.as_ref()) {
|
||||
Err(e) => {
|
||||
return NetworkResult::invalid_message(e.to_string());
|
||||
}
|
||||
@ -649,10 +663,9 @@ impl NetworkManager {
|
||||
let rpc = self.rpc_processor();
|
||||
|
||||
// Add the peer info to our routing table
|
||||
let peer_nr = match routing_table.register_node_with_signed_node_info(
|
||||
let peer_nr = match routing_table.register_node_with_peer_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
peer_info.node_id.key,
|
||||
peer_info.signed_node_info,
|
||||
peer_info,
|
||||
false,
|
||||
) {
|
||||
None => {
|
||||
@ -673,10 +686,9 @@ impl NetworkManager {
|
||||
let rpc = self.rpc_processor();
|
||||
|
||||
// Add the peer info to our routing table
|
||||
let mut peer_nr = match routing_table.register_node_with_signed_node_info(
|
||||
let mut peer_nr = match routing_table.register_node_with_peer_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
peer_info.node_id.key,
|
||||
peer_info.signed_node_info,
|
||||
peer_info,
|
||||
false,
|
||||
) {
|
||||
None => {
|
||||
@ -731,21 +743,25 @@ impl NetworkManager {
|
||||
#[instrument(level = "trace", skip(self, body), err)]
|
||||
fn build_envelope<B: AsRef<[u8]>>(
|
||||
&self,
|
||||
dest_node_id: DHTKey,
|
||||
dest_node_id: TypedKey,
|
||||
version: u8,
|
||||
body: B,
|
||||
) -> EyreResult<Vec<u8>> {
|
||||
// DH to get encryption key
|
||||
let routing_table = self.routing_table();
|
||||
let node_id = routing_table.node_id();
|
||||
let node_id_secret = routing_table.node_id_secret();
|
||||
let Some(vcrypto) = self.crypto().get(dest_node_id.kind) else {
|
||||
bail!("should not have a destination with incompatible crypto here");
|
||||
};
|
||||
|
||||
let node_id = routing_table.node_id(vcrypto.kind());
|
||||
let node_id_secret = routing_table.node_id_secret_key(vcrypto.kind());
|
||||
|
||||
// Get timestamp, nonce
|
||||
let ts = get_aligned_timestamp();
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let nonce = vcrypto.random_nonce();
|
||||
|
||||
// Encode envelope
|
||||
let envelope = Envelope::new(version, ts, nonce, node_id, dest_node_id);
|
||||
let envelope = Envelope::new(version, node_id.kind, ts, nonce, node_id.value, dest_node_id.value);
|
||||
envelope
|
||||
.to_encrypted_data(self.crypto(), body.as_ref(), &node_id_secret)
|
||||
.wrap_err("envelope failed to encode")
|
||||
@ -753,50 +769,44 @@ impl NetworkManager {
|
||||
|
||||
/// Called by the RPC handler when we want to issue an RPC request or response
|
||||
/// node_ref is the direct destination to which the envelope will be sent
|
||||
/// If 'node_id' is specified, it can be different than node_ref.node_id()
|
||||
/// If 'destination_node_ref' is specified, it can be different than the node_ref being sent to
|
||||
/// which will cause the envelope to be relayed
|
||||
#[instrument(level = "trace", skip(self, body), ret, err)]
|
||||
pub async fn send_envelope<B: AsRef<[u8]>>(
|
||||
&self,
|
||||
node_ref: NodeRef,
|
||||
envelope_node_id: Option<DHTKey>,
|
||||
destination_node_ref: Option<NodeRef>,
|
||||
body: B,
|
||||
) -> EyreResult<NetworkResult<SendDataKind>> {
|
||||
let via_node_id = node_ref.node_id();
|
||||
let envelope_node_id = envelope_node_id.unwrap_or(via_node_id);
|
||||
|
||||
if envelope_node_id != via_node_id {
|
||||
let destination_node_ref = destination_node_ref.as_ref().unwrap_or(&node_ref).clone();
|
||||
|
||||
if !node_ref.same_entry(&destination_node_ref) {
|
||||
log_net!(
|
||||
"sending envelope to {:?} via {:?}",
|
||||
envelope_node_id,
|
||||
destination_node_ref,
|
||||
node_ref
|
||||
);
|
||||
} else {
|
||||
log_net!("sending envelope to {:?}", node_ref);
|
||||
}
|
||||
// Get node's min/max version and see if we can send to it
|
||||
|
||||
let best_node_id = destination_node_ref.best_node_id();
|
||||
|
||||
// Get node's envelope versions and see if we can send to it
|
||||
// and if so, get the max version we can use
|
||||
let version = if let Some(min_max_version) = node_ref.min_max_version() {
|
||||
#[allow(clippy::absurd_extreme_comparisons)]
|
||||
if min_max_version.min > MAX_CRYPTO_VERSION || min_max_version.max < MIN_CRYPTO_VERSION
|
||||
{
|
||||
bail!(
|
||||
"can't talk to this node {} because version is unsupported: ({},{})",
|
||||
via_node_id,
|
||||
min_max_version.min,
|
||||
min_max_version.max
|
||||
);
|
||||
}
|
||||
cmp::min(min_max_version.max, MAX_CRYPTO_VERSION)
|
||||
} else {
|
||||
MAX_CRYPTO_VERSION
|
||||
let Some(envelope_version) = destination_node_ref.best_envelope_version() else {
|
||||
bail!(
|
||||
"can't talk to this node {} because we dont support its envelope versions",
|
||||
node_ref
|
||||
);
|
||||
};
|
||||
|
||||
// Build the envelope to send
|
||||
let out = self.build_envelope(envelope_node_id, version, body)?;
|
||||
let out = self.build_envelope(best_node_id, envelope_version, body)?;
|
||||
|
||||
// Send the envelope via whatever means necessary
|
||||
self.send_data(node_ref.clone(), out).await
|
||||
self.send_data(node_ref, out).await
|
||||
}
|
||||
|
||||
/// Called by the RPC handler when we want to issue an direct receipt
|
||||
@ -860,7 +870,7 @@ impl NetworkManager {
|
||||
let rpc = self.rpc_processor();
|
||||
network_result_try!(rpc
|
||||
.rpc_call_signal(
|
||||
Destination::relay(relay_nr, target_nr.node_id()),
|
||||
Destination::relay(relay_nr, target_nr.clone()),
|
||||
SignalInfo::ReverseConnect { receipt, peer_info },
|
||||
)
|
||||
.await
|
||||
@ -886,7 +896,7 @@ impl NetworkManager {
|
||||
|
||||
// We expect the inbound noderef to be the same as the target noderef
|
||||
// if they aren't the same, we should error on this and figure out what then hell is up
|
||||
if target_nr.node_id() != inbound_nr.node_id() {
|
||||
if !target_nr.same_entry(&inbound_nr) {
|
||||
bail!("unexpected noderef mismatch on reverse connect");
|
||||
}
|
||||
|
||||
@ -965,7 +975,7 @@ impl NetworkManager {
|
||||
let rpc = self.rpc_processor();
|
||||
network_result_try!(rpc
|
||||
.rpc_call_signal(
|
||||
Destination::relay(relay_nr, target_nr.node_id()),
|
||||
Destination::relay(relay_nr, target_nr.clone()),
|
||||
SignalInfo::HolePunch { receipt, peer_info },
|
||||
)
|
||||
.await
|
||||
@ -991,7 +1001,7 @@ impl NetworkManager {
|
||||
|
||||
// We expect the inbound noderef to be the same as the target noderef
|
||||
// if they aren't the same, we should error on this and figure out what then hell is up
|
||||
if target_nr.node_id() != inbound_nr.node_id() {
|
||||
if !target_nr.same_entry(&inbound_nr) {
|
||||
bail!(
|
||||
"unexpected noderef mismatch on hole punch {}, expected {}",
|
||||
inbound_nr,
|
||||
@ -1069,7 +1079,7 @@ impl NetworkManager {
|
||||
let relay_nr = routing_table
|
||||
.lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)
|
||||
.ok_or_else(|| eyre!("couldn't look up relay"))?;
|
||||
if target_node_ref.node_id() != target_key {
|
||||
if !target_node_ref.node_ids().contains(&target_key) {
|
||||
bail!("target noderef didn't match target key");
|
||||
}
|
||||
NodeContactMethod::SignalReverse(relay_nr, target_node_ref)
|
||||
@ -1078,7 +1088,7 @@ impl NetworkManager {
|
||||
let relay_nr = routing_table
|
||||
.lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)
|
||||
.ok_or_else(|| eyre!("couldn't look up relay"))?;
|
||||
if target_node_ref.node_id() != target_key {
|
||||
if target_node_ref.node_ids().contains(&target_key) {
|
||||
bail!("target noderef didn't match target key");
|
||||
}
|
||||
NodeContactMethod::SignalHolePunch(relay_nr, target_node_ref)
|
||||
@ -1320,13 +1330,13 @@ impl NetworkManager {
|
||||
}
|
||||
|
||||
// Is this an out-of-band receipt instead of an envelope?
|
||||
if data[0..4] == *RECEIPT_MAGIC {
|
||||
if data[0..3] == *RECEIPT_MAGIC {
|
||||
network_result_value_or_log!(self.handle_out_of_band_receipt(data).await => {});
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Decode envelope header (may fail signature validation)
|
||||
let envelope = match Envelope::from_signed_data(data) {
|
||||
let envelope = match Envelope::from_signed_data(self.crypto(), data) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_net!(debug "envelope failed to decode: {}", e);
|
||||
@ -1370,16 +1380,16 @@ impl NetworkManager {
|
||||
|
||||
// Peek at header and see if we need to relay this
|
||||
// If the recipient id is not our node id, then it needs relaying
|
||||
let sender_id = envelope.get_sender_id();
|
||||
let recipient_id = envelope.get_recipient_id();
|
||||
if recipient_id != routing_table.node_id() {
|
||||
let sender_id = TypedKey::new(envelope.get_crypto_kind(), envelope.get_sender_id());
|
||||
let recipient_id = TypedKey::new(envelope.get_crypto_kind(), envelope.get_recipient_id());
|
||||
if !routing_table.matches_own_node_id(&[recipient_id]) {
|
||||
// See if the source node is allowed to resolve nodes
|
||||
// This is a costly operation, so only outbound-relay permitted
|
||||
// nodes are allowed to do this, for example PWA users
|
||||
|
||||
let some_relay_nr = if self.check_client_whitelist(sender_id) {
|
||||
// Full relay allowed, do a full resolve_node
|
||||
match rpc.resolve_node(recipient_id).await {
|
||||
match rpc.resolve_node(recipient_id.value).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_net!(debug "failed to resolve recipient node for relay, dropping outbound relayed packet: {}" ,e);
|
||||
@ -1417,7 +1427,7 @@ impl NetworkManager {
|
||||
}
|
||||
|
||||
// DH to get decryption key (cached)
|
||||
let node_id_secret = routing_table.node_id_secret();
|
||||
let node_id_secret = routing_table.node_id_secret_key(envelope.get_crypto_kind());
|
||||
|
||||
// Decrypt the envelope body
|
||||
let body = match envelope
|
||||
@ -1432,7 +1442,7 @@ impl NetworkManager {
|
||||
|
||||
// Cache the envelope information in the routing table
|
||||
let source_noderef = match routing_table.register_node_with_existing_connection(
|
||||
envelope.get_sender_id(),
|
||||
TypedKey::new(envelope.get_crypto_kind(), envelope.get_sender_id()),
|
||||
connection_descriptor,
|
||||
ts,
|
||||
) {
|
||||
@ -1443,7 +1453,7 @@ impl NetworkManager {
|
||||
}
|
||||
Some(v) => v,
|
||||
};
|
||||
source_noderef.set_min_max_version(envelope.get_min_max_version());
|
||||
source_noderef.add_envelope_version(envelope.get_version());
|
||||
|
||||
// xxx: deal with spoofing and flooding here?
|
||||
|
||||
@ -1471,7 +1481,9 @@ impl NetworkManager {
|
||||
inner
|
||||
.stats
|
||||
.per_address_stats
|
||||
.entry(PerAddressStatsKey(addr))
|
||||
.entry(PerAddressStatsKey(addr), |_k,_v| {
|
||||
// do nothing on LRU evict
|
||||
})
|
||||
.or_insert(PerAddressStats::default())
|
||||
.transfer_stats_accounting
|
||||
.add_up(bytes);
|
||||
@ -1487,7 +1499,9 @@ impl NetworkManager {
|
||||
inner
|
||||
.stats
|
||||
.per_address_stats
|
||||
.entry(PerAddressStatsKey(addr))
|
||||
.entry(PerAddressStatsKey(addr), |_k,_v| {
|
||||
// do nothing on LRU evict
|
||||
})
|
||||
.or_insert(PerAddressStats::default())
|
||||
.transfer_stats_accounting
|
||||
.add_down(bytes);
|
||||
@ -1537,7 +1551,7 @@ impl NetworkManager {
|
||||
if let Some(nr) = routing_table.lookup_node_ref(k) {
|
||||
let peer_stats = nr.peer_stats();
|
||||
let peer = PeerTableData {
|
||||
node_id: k,
|
||||
node_ids: nr.node_ids(),
|
||||
peer_address: v.last_connection.remote(),
|
||||
peer_stats,
|
||||
};
|
||||
@ -1622,7 +1636,9 @@ impl NetworkManager {
|
||||
if pait.contains_key(&ipblock) {
|
||||
return;
|
||||
}
|
||||
pacc.insert(ipblock, socket_address);
|
||||
pacc.insert(ipblock, socket_address, |_k,_v| {
|
||||
// do nothing on LRU evict
|
||||
});
|
||||
|
||||
// Determine if our external address has likely changed
|
||||
let mut bad_public_address_detection_punishment: Option<
|
||||
|
@ -112,7 +112,7 @@ impl DiscoveryContext {
|
||||
&self,
|
||||
protocol_type: ProtocolType,
|
||||
address_type: AddressType,
|
||||
ignore_node: Option<DHTKey>,
|
||||
ignore_node_ids: Option<TypedKeySet>,
|
||||
) -> Option<(SocketAddress, NodeRef)> {
|
||||
let node_count = {
|
||||
let config = self.routing_table.network_manager().config();
|
||||
@ -121,7 +121,7 @@ impl DiscoveryContext {
|
||||
};
|
||||
|
||||
// Build an filter that matches our protocol and address type
|
||||
// and excludes relays so we can get an accurate external address
|
||||
// and excludes relayed nodes so we can get an accurate external address
|
||||
let dial_info_filter = DialInfoFilter::all()
|
||||
.with_protocol_type(protocol_type)
|
||||
.with_address_type(address_type);
|
||||
@ -130,11 +130,11 @@ impl DiscoveryContext {
|
||||
dial_info_filter.clone(),
|
||||
);
|
||||
let disallow_relays_filter = Box::new(
|
||||
move |rti: &RoutingTableInner, _k: DHTKey, v: Option<Arc<BucketEntry>>| {
|
||||
move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
|
||||
let v = v.unwrap();
|
||||
v.with(rti, |_rti, e| {
|
||||
if let Some(n) = e.signed_node_info(RoutingDomain::PublicInternet) {
|
||||
n.relay_id().is_none()
|
||||
n.relay_ids().is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@ -158,8 +158,8 @@ impl DiscoveryContext {
|
||||
|
||||
// For each peer, if it's not our ignore-node, ask them for our public address, filtering on desired dial info
|
||||
for mut peer in peers {
|
||||
if let Some(ignore_node) = ignore_node {
|
||||
if peer.node_id() == ignore_node {
|
||||
if let Some(ignore_node_ids) = &ignore_node_ids {
|
||||
if peer.node_ids().contains_any(ignore_node_ids) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -478,12 +478,12 @@ impl DiscoveryContext {
|
||||
|
||||
// Get our external address from some fast node, that is not node 1, call it node 2
|
||||
let (external_2_address, node_2) = match self
|
||||
.discover_external_address(protocol_type, address_type, Some(node_1.node_id()))
|
||||
.discover_external_address(protocol_type, address_type, Some(node_1.node_ids()))
|
||||
.await
|
||||
{
|
||||
None => {
|
||||
// If we can't get an external address, allow retry
|
||||
log_net!(debug "failed to discover external address 2 for {:?}:{:?}, skipping node {:?}", protocol_type, address_type, node_1.node_id());
|
||||
log_net!(debug "failed to discover external address 2 for {:?}:{:?}, skipping node {:?}", protocol_type, address_type, node_1);
|
||||
return Ok(false);
|
||||
}
|
||||
Some(v) => v,
|
||||
|
@ -11,7 +11,7 @@ pub enum ReceiptEvent {
|
||||
ReturnedOutOfBand,
|
||||
ReturnedInBand { inbound_noderef: NodeRef },
|
||||
ReturnedSafety,
|
||||
ReturnedPrivate { private_route: DHTKey },
|
||||
ReturnedPrivate { private_route: PublicKey },
|
||||
Expired,
|
||||
Cancelled,
|
||||
}
|
||||
@ -21,7 +21,7 @@ pub enum ReceiptReturned {
|
||||
OutOfBand,
|
||||
InBand { inbound_noderef: NodeRef },
|
||||
Safety,
|
||||
Private { private_route: DHTKey },
|
||||
Private { private_route: PublicKey },
|
||||
}
|
||||
|
||||
pub trait ReceiptCallback: Send + 'static {
|
||||
@ -149,7 +149,7 @@ impl PartialOrd for ReceiptRecordTimestampSort {
|
||||
|
||||
pub struct ReceiptManagerInner {
|
||||
network_manager: NetworkManager,
|
||||
records_by_nonce: BTreeMap<ReceiptNonce, Arc<Mutex<ReceiptRecord>>>,
|
||||
records_by_nonce: BTreeMap<Nonce, Arc<Mutex<ReceiptRecord>>>,
|
||||
next_oldest_ts: Option<Timestamp>,
|
||||
stop_source: Option<StopSource>,
|
||||
timeout_task: MustJoinSingleFuture<()>,
|
||||
@ -370,7 +370,7 @@ impl ReceiptManager {
|
||||
inner.next_oldest_ts = new_next_oldest_ts;
|
||||
}
|
||||
|
||||
pub async fn cancel_receipt(&self, nonce: &ReceiptNonce) -> EyreResult<()> {
|
||||
pub async fn cancel_receipt(&self, nonce: &Nonce) -> EyreResult<()> {
|
||||
log_rpc!(debug "== Cancel Receipt {}", nonce.encode());
|
||||
|
||||
// Remove the record
|
||||
|
@ -2,25 +2,31 @@ use super::*;
|
||||
use core::sync::atomic::Ordering;
|
||||
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
|
||||
/// Routing Table Bucket
|
||||
/// Stores map of public keys to entries, which may be in multiple routing tables per crypto kind
|
||||
/// Keeps entries at a particular 'dht distance' from this cryptokind's node id
|
||||
/// Helps to keep managed lists at particular distances so we can evict nodes by priority
|
||||
/// where the priority comes from liveness and age of the entry (older is better)
|
||||
pub struct Bucket {
|
||||
routing_table: RoutingTable,
|
||||
entries: BTreeMap<DHTKey, Arc<BucketEntry>>,
|
||||
newest_entry: Option<DHTKey>,
|
||||
/// Map of keys to entries for this bucket
|
||||
entries: BTreeMap<PublicKey, Arc<BucketEntry>>,
|
||||
/// The crypto kind in use for the public keys in this bucket
|
||||
kind: CryptoKind,
|
||||
}
|
||||
pub(super) type EntriesIter<'a> = alloc::collections::btree_map::Iter<'a, DHTKey, Arc<BucketEntry>>;
|
||||
pub(super) type EntriesIter<'a> =
|
||||
alloc::collections::btree_map::Iter<'a, PublicKey, Arc<BucketEntry>>;
|
||||
|
||||
#[derive(Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
struct BucketEntryData {
|
||||
key: DHTKey,
|
||||
value: Vec<u8>,
|
||||
struct SerializedBucketEntryData {
|
||||
key: PublicKey,
|
||||
value: u32, // index into serialized entries list
|
||||
}
|
||||
|
||||
#[derive(Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
struct BucketData {
|
||||
entries: Vec<BucketEntryData>,
|
||||
newest_entry: Option<DHTKey>,
|
||||
struct SerializedBucketData {
|
||||
entries: Vec<SerializedBucketEntryData>,
|
||||
}
|
||||
|
||||
fn state_ordering(state: BucketEntryState) -> usize {
|
||||
@ -32,68 +38,79 @@ fn state_ordering(state: BucketEntryState) -> usize {
|
||||
}
|
||||
|
||||
impl Bucket {
|
||||
pub fn new(routing_table: RoutingTable) -> Self {
|
||||
pub fn new(kind: CryptoKind) -> Self {
|
||||
Self {
|
||||
routing_table,
|
||||
entries: BTreeMap::new(),
|
||||
newest_entry: None,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn load_bucket(&mut self, data: Vec<u8>) -> EyreResult<()> {
|
||||
let bucket_data: BucketData = from_rkyv(data)?;
|
||||
pub(super) fn load_bucket(
|
||||
&mut self,
|
||||
data: Vec<u8>,
|
||||
all_entries: &[Arc<BucketEntry>],
|
||||
) -> EyreResult<()> {
|
||||
let bucket_data: SerializedBucketData = from_rkyv(data)?;
|
||||
|
||||
for e in bucket_data.entries {
|
||||
let entryinner = from_rkyv(e.value).wrap_err("failed to deserialize bucket entry")?;
|
||||
self.entries
|
||||
.insert(e.key, Arc::new(BucketEntry::new_with_inner(entryinner)));
|
||||
.insert(e.key, all_entries[e.value as usize].clone());
|
||||
}
|
||||
|
||||
self.newest_entry = bucket_data.newest_entry;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub(super) fn save_bucket(&self) -> EyreResult<Vec<u8>> {
|
||||
|
||||
pub(super) fn save_bucket(
|
||||
&self,
|
||||
all_entries: &mut Vec<Arc<BucketEntry>>,
|
||||
entry_map: &mut HashMap<*const BucketEntry, u32>,
|
||||
) -> EyreResult<Vec<u8>> {
|
||||
let mut entries = Vec::new();
|
||||
for (k, v) in &self.entries {
|
||||
let entry_bytes = v.with_inner(|e| to_rkyv(e))?;
|
||||
entries.push(BucketEntryData {
|
||||
let entry_index = entry_map.entry(Arc::as_ptr(v)).or_insert_with(|| {
|
||||
let entry_index = all_entries.len();
|
||||
all_entries.push(v.clone());
|
||||
entry_index as u32
|
||||
});
|
||||
entries.push(SerializedBucketEntryData {
|
||||
key: *k,
|
||||
value: entry_bytes,
|
||||
value: *entry_index,
|
||||
});
|
||||
}
|
||||
let bucket_data = BucketData {
|
||||
entries,
|
||||
newest_entry: self.newest_entry.clone(),
|
||||
};
|
||||
let bucket_data = SerializedBucketData { entries };
|
||||
let out = to_rkyv(&bucket_data)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub(super) fn add_entry(&mut self, node_id: DHTKey) -> NodeRef {
|
||||
log_rtab!("Node added: {}", node_id.encode());
|
||||
/// Create a new entry with a node_id of this crypto kind and return it
|
||||
pub(super) fn add_new_entry(&mut self, node_id_key: PublicKey) -> Arc<BucketEntry> {
|
||||
log_rtab!("Node added: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Add new entry
|
||||
self.entries.insert(node_id, Arc::new(BucketEntry::new()));
|
||||
let entry = Arc::new(BucketEntry::new(TypedKey::new(self.kind, node_id_key)));
|
||||
self.entries.insert(node_id_key, entry.clone());
|
||||
|
||||
// This is now the newest bucket entry
|
||||
self.newest_entry = Some(node_id);
|
||||
|
||||
// Get a node ref to return
|
||||
let entry = self.entries.get(&node_id).unwrap().clone();
|
||||
NodeRef::new(self.routing_table.clone(), node_id, entry, None)
|
||||
// Return the new entry
|
||||
entry
|
||||
}
|
||||
|
||||
pub(super) fn remove_entry(&mut self, node_id: &DHTKey) {
|
||||
log_rtab!("Node removed: {}", node_id);
|
||||
/// Add an existing entry with a new node_id for this crypto kind
|
||||
pub(super) fn add_existing_entry(&mut self, node_id_key: PublicKey, entry: Arc<BucketEntry>) {
|
||||
log_rtab!("Existing node added: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Add existing entry
|
||||
self.entries.insert(node_id_key, entry);
|
||||
}
|
||||
|
||||
/// Remove an entry with a node_id for this crypto kind from the bucket
|
||||
pub(super) fn remove_entry(&mut self, node_id_key: &PublicKey) {
|
||||
log_rtab!("Node removed: {}:{}", self.kind, node_id_key);
|
||||
|
||||
// Remove the entry
|
||||
self.entries.remove(node_id);
|
||||
|
||||
// newest_entry is updated by kick_bucket()
|
||||
self.entries.remove(node_id_key);
|
||||
}
|
||||
|
||||
pub(super) fn entry(&self, key: &DHTKey) -> Option<Arc<BucketEntry>> {
|
||||
pub(super) fn entry(&self, key: &PublicKey) -> Option<Arc<BucketEntry>> {
|
||||
self.entries.get(key).cloned()
|
||||
}
|
||||
|
||||
@ -101,7 +118,7 @@ impl Bucket {
|
||||
self.entries.iter()
|
||||
}
|
||||
|
||||
pub(super) fn kick(&mut self, bucket_depth: usize) -> Option<BTreeSet<DHTKey>> {
|
||||
pub(super) fn kick(&mut self, bucket_depth: usize) -> Option<BTreeSet<PublicKey>> {
|
||||
// Get number of entries to attempt to purge from bucket
|
||||
let bucket_len = self.entries.len();
|
||||
|
||||
@ -111,11 +128,11 @@ impl Bucket {
|
||||
}
|
||||
|
||||
// Try to purge the newest entries that overflow the bucket
|
||||
let mut dead_node_ids: BTreeSet<DHTKey> = BTreeSet::new();
|
||||
let mut dead_node_ids: BTreeSet<PublicKey> = BTreeSet::new();
|
||||
let mut extra_entries = bucket_len - bucket_depth;
|
||||
|
||||
// Get the sorted list of entries by their kick order
|
||||
let mut sorted_entries: Vec<(DHTKey, Arc<BucketEntry>)> = self
|
||||
let mut sorted_entries: Vec<(PublicKey, Arc<BucketEntry>)> = self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
@ -144,24 +161,15 @@ impl Bucket {
|
||||
})
|
||||
});
|
||||
|
||||
self.newest_entry = None;
|
||||
for entry in sorted_entries {
|
||||
// If we're not evicting more entries, exit, noting this may be the newest entry
|
||||
if extra_entries == 0 {
|
||||
// The first 'live' entry we find is our newest entry
|
||||
if self.newest_entry.is_none() {
|
||||
self.newest_entry = Some(entry.0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_entries -= 1;
|
||||
|
||||
// if this entry has references we can't drop it yet
|
||||
if entry.1.ref_count.load(Ordering::Acquire) > 0 {
|
||||
// The first 'live' entry we fine is our newest entry
|
||||
if self.newest_entry.is_none() {
|
||||
self.newest_entry = Some(entry.0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -68,23 +68,14 @@ pub struct BucketEntryLocalNetwork {
|
||||
node_status: Option<LocalNetworkNodeStatus>,
|
||||
}
|
||||
|
||||
/// A range of cryptography versions supported by this entry
|
||||
#[derive(Copy, Clone, Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct VersionRange {
|
||||
/// The minimum cryptography version supported by this entry
|
||||
pub min: u8,
|
||||
/// The maximum cryptography version supported by this entry
|
||||
pub max: u8,
|
||||
}
|
||||
|
||||
/// The data associated with each bucket entry
|
||||
#[derive(Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct BucketEntryInner {
|
||||
/// The minimum and maximum range of cryptography versions supported by the node,
|
||||
/// inclusive of the requirements of any relay the node may be using
|
||||
min_max_version: Option<VersionRange>,
|
||||
/// The node ids matching this bucket entry, with the cryptography versions supported by this node as the 'kind' field
|
||||
node_ids: TypedKeySet,
|
||||
/// The set of envelope versions supported by the node inclusive of the requirements of any relay the node may be using
|
||||
envelope_support: Vec<u8>,
|
||||
/// If this node has updated it's SignedNodeInfo since our network
|
||||
/// and dial info has last changed, for example when our IP address changes
|
||||
/// Used to determine if we should make this entry 'live' again when we receive a signednodeinfo update that
|
||||
@ -131,6 +122,38 @@ impl BucketEntryInner {
|
||||
self.node_ref_tracks.remove(&track_id);
|
||||
}
|
||||
|
||||
/// Get node ids
|
||||
pub fn node_ids(&self) -> TypedKeySet {
|
||||
self.node_ids.clone()
|
||||
}
|
||||
/// Add a node id for a particular crypto kind.
|
||||
/// Returns any previous existing node id associated with that crypto kind
|
||||
pub fn add_node_id(&mut self, node_id: TypedKey) -> Option<TypedKey> {
|
||||
if let Some(old_node_id) = self.node_ids.get(node_id.kind) {
|
||||
// If this was already there we do nothing
|
||||
if old_node_id == node_id {
|
||||
return None;
|
||||
}
|
||||
self.node_ids.add(node_id);
|
||||
return Some(old_node_id);
|
||||
}
|
||||
self.node_ids.add(node_id);
|
||||
None
|
||||
}
|
||||
pub fn best_node_id(&self) -> TypedKey {
|
||||
self.node_ids.best().unwrap()
|
||||
}
|
||||
|
||||
/// Get crypto kinds
|
||||
pub fn crypto_kinds(&self) -> Vec<CryptoKind> {
|
||||
self.node_ids.kinds()
|
||||
}
|
||||
/// Compare sets of crypto kinds
|
||||
pub fn common_crypto_kinds(&self, other: &[CryptoKind]) -> Vec<CryptoKind> {
|
||||
common_crypto_kinds(&self.node_ids.kinds(), other)
|
||||
}
|
||||
|
||||
|
||||
// Less is faster
|
||||
pub fn cmp_fastest(e1: &Self, e2: &Self) -> std::cmp::Ordering {
|
||||
// Lower latency to the front
|
||||
@ -219,7 +242,7 @@ impl BucketEntryInner {
|
||||
// See if we have an existing signed_node_info to update or not
|
||||
if let Some(current_sni) = opt_current_sni {
|
||||
// Always allow overwriting invalid/unsigned node
|
||||
if current_sni.has_valid_signature() {
|
||||
if current_sni.has_any_signature() {
|
||||
// If the timestamp hasn't changed or is less, ignore this update
|
||||
if signed_node_info.timestamp() <= current_sni.timestamp() {
|
||||
// If we received a node update with the same timestamp
|
||||
@ -238,25 +261,12 @@ impl BucketEntryInner {
|
||||
}
|
||||
}
|
||||
|
||||
// Update the protocol min/max version we have to use, to include relay requirements if needed
|
||||
let mut version_range = VersionRange {
|
||||
min: signed_node_info.node_info().min_version,
|
||||
max: signed_node_info.node_info().max_version,
|
||||
};
|
||||
if let Some(relay_info) = signed_node_info.relay_info() {
|
||||
version_range.min.max_assign(relay_info.min_version);
|
||||
version_range.max.min_assign(relay_info.max_version);
|
||||
}
|
||||
if version_range.min <= version_range.max {
|
||||
// Can be reached with at least one crypto version
|
||||
self.min_max_version = Some(version_range);
|
||||
} else {
|
||||
// No valid crypto version in range
|
||||
self.min_max_version = None;
|
||||
}
|
||||
|
||||
// Update the envelope version support we have to use
|
||||
let envelope_support = signed_node_info.node_info().envelope_support.clone();
|
||||
|
||||
// Update the signed node info
|
||||
*opt_current_sni = Some(Box::new(signed_node_info));
|
||||
self.set_envelope_support(envelope_support);
|
||||
self.updated_since_last_network_change = true;
|
||||
self.touch_last_seen(get_aligned_timestamp());
|
||||
}
|
||||
@ -310,13 +320,13 @@ impl BucketEntryInner {
|
||||
opt_current_sni.as_ref().map(|s| s.as_ref())
|
||||
}
|
||||
|
||||
pub fn make_peer_info(&self, key: DHTKey, routing_domain: RoutingDomain) -> Option<PeerInfo> {
|
||||
pub fn make_peer_info(&self, routing_domain: RoutingDomain) -> Option<PeerInfo> {
|
||||
let opt_current_sni = match routing_domain {
|
||||
RoutingDomain::LocalNetwork => &self.local_network.signed_node_info,
|
||||
RoutingDomain::PublicInternet => &self.public_internet.signed_node_info,
|
||||
};
|
||||
opt_current_sni.as_ref().map(|s| PeerInfo {
|
||||
node_id: NodeId::new(key),
|
||||
node_ids: self.node_ids.clone(),
|
||||
signed_node_info: *s.clone(),
|
||||
})
|
||||
}
|
||||
@ -447,12 +457,27 @@ impl BucketEntryInner {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn set_min_max_version(&mut self, min_max_version: VersionRange) {
|
||||
self.min_max_version = Some(min_max_version);
|
||||
pub fn add_envelope_version(&mut self, envelope_version: u8) {
|
||||
if self.envelope_support.contains(&envelope_version) {
|
||||
return;
|
||||
}
|
||||
self.envelope_support.push(envelope_version);
|
||||
self.envelope_support.dedup();
|
||||
self.envelope_support.sort();
|
||||
}
|
||||
|
||||
pub fn min_max_version(&self) -> Option<VersionRange> {
|
||||
self.min_max_version
|
||||
pub fn set_envelope_support(&mut self, mut envelope_support: Vec<u8>) {
|
||||
envelope_support.dedup();
|
||||
envelope_support.sort();
|
||||
self.envelope_support = envelope_support;
|
||||
}
|
||||
|
||||
pub fn envelope_support(&self) -> Vec<u8> {
|
||||
self.envelope_support.clone()
|
||||
}
|
||||
|
||||
pub fn best_envelope_version(&self) -> Option<u8> {
|
||||
self.envelope_support.iter().rev().find(|x| VALID_ENVELOPE_VERSIONS.contains(x)).copied()
|
||||
}
|
||||
|
||||
pub fn state(&self, cur_ts: Timestamp) -> BucketEntryState {
|
||||
@ -746,38 +771,41 @@ pub struct BucketEntry {
|
||||
}
|
||||
|
||||
impl BucketEntry {
|
||||
pub(super) fn new() -> Self {
|
||||
pub(super) fn new(first_node_id: TypedKey) -> Self {
|
||||
let now = get_aligned_timestamp();
|
||||
Self {
|
||||
ref_count: AtomicU32::new(0),
|
||||
inner: RwLock::new(BucketEntryInner {
|
||||
min_max_version: None,
|
||||
updated_since_last_network_change: false,
|
||||
last_connections: BTreeMap::new(),
|
||||
local_network: BucketEntryLocalNetwork {
|
||||
last_seen_our_node_info_ts: Timestamp::new(0u64),
|
||||
signed_node_info: None,
|
||||
node_status: None,
|
||||
},
|
||||
public_internet: BucketEntryPublicInternet {
|
||||
last_seen_our_node_info_ts: Timestamp::new(0u64),
|
||||
signed_node_info: None,
|
||||
node_status: None,
|
||||
},
|
||||
peer_stats: PeerStats {
|
||||
time_added: now,
|
||||
rpc_stats: RPCStats::default(),
|
||||
latency: None,
|
||||
transfer: TransferStatsDownUp::default(),
|
||||
},
|
||||
latency_stats_accounting: LatencyStatsAccounting::new(),
|
||||
transfer_stats_accounting: TransferStatsAccounting::new(),
|
||||
#[cfg(feature = "tracking")]
|
||||
next_track_id: 0,
|
||||
#[cfg(feature = "tracking")]
|
||||
node_ref_tracks: HashMap::new(),
|
||||
}),
|
||||
}
|
||||
let mut node_ids = TypedKeySet::new();
|
||||
node_ids.add(first_node_id);
|
||||
|
||||
let inner = BucketEntryInner {
|
||||
node_ids,
|
||||
envelope_support: Vec::new(),
|
||||
updated_since_last_network_change: false,
|
||||
last_connections: BTreeMap::new(),
|
||||
local_network: BucketEntryLocalNetwork {
|
||||
last_seen_our_node_info_ts: Timestamp::new(0u64),
|
||||
signed_node_info: None,
|
||||
node_status: None,
|
||||
},
|
||||
public_internet: BucketEntryPublicInternet {
|
||||
last_seen_our_node_info_ts: Timestamp::new(0u64),
|
||||
signed_node_info: None,
|
||||
node_status: None,
|
||||
},
|
||||
peer_stats: PeerStats {
|
||||
time_added: now,
|
||||
rpc_stats: RPCStats::default(),
|
||||
latency: None,
|
||||
transfer: TransferStatsDownUp::default(),
|
||||
},
|
||||
latency_stats_accounting: LatencyStatsAccounting::new(),
|
||||
transfer_stats_accounting: TransferStatsAccounting::new(),
|
||||
#[cfg(feature = "tracking")]
|
||||
next_track_id: 0,
|
||||
#[cfg(feature = "tracking")]
|
||||
node_ref_tracks: HashMap::new(),
|
||||
};
|
||||
|
||||
Self::new_with_inner(inner)
|
||||
}
|
||||
|
||||
pub(super) fn new_with_inner(inner: BucketEntryInner) -> Self {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use routing_table::tasks::bootstrap::BOOTSTRAP_TXT_VERSION;
|
||||
use routing_table::tasks::bootstrap::BOOTSTRAP_TXT_VERSION_0;
|
||||
|
||||
impl RoutingTable {
|
||||
pub(crate) fn debug_info_nodeinfo(&self) -> String {
|
||||
@ -7,7 +7,7 @@ impl RoutingTable {
|
||||
let inner = self.inner.read();
|
||||
out += "Routing Table Info:\n";
|
||||
|
||||
out += &format!(" Node Id: {}\n", self.unlocked_inner.node_id.encode());
|
||||
out += &format!(" Node Ids: {}\n", self.unlocked_inner.node_ids());
|
||||
out += &format!(
|
||||
" Self Latency Stats Accounting: {:#?}\n\n",
|
||||
inner.self_latency_stats_accounting
|
||||
@ -55,13 +55,20 @@ impl RoutingTable {
|
||||
short_urls.sort();
|
||||
short_urls.dedup();
|
||||
|
||||
let valid_envelope_versions = VALID_ENVELOPE_VERSIONS.map(|x| x.to_string()).join(",");
|
||||
let node_ids = self
|
||||
.unlocked_inner
|
||||
.node_ids()
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
out += "TXT Record:\n";
|
||||
out += &format!(
|
||||
"{},{},{},{},{}",
|
||||
BOOTSTRAP_TXT_VERSION,
|
||||
MIN_CRYPTO_VERSION,
|
||||
MAX_CRYPTO_VERSION,
|
||||
self.node_id().encode(),
|
||||
"{}|{}|{}|{}|",
|
||||
BOOTSTRAP_TXT_VERSION_0,
|
||||
valid_envelope_versions,
|
||||
node_ids,
|
||||
some_hostname.unwrap()
|
||||
);
|
||||
for short_url in short_urls {
|
||||
@ -108,56 +115,53 @@ impl RoutingTable {
|
||||
|
||||
let mut out = String::new();
|
||||
|
||||
let blen = inner.buckets.len();
|
||||
let mut b = 0;
|
||||
let mut cnt = 0;
|
||||
out += &format!("Entries: {}\n", inner.bucket_entry_count);
|
||||
while b < blen {
|
||||
let filtered_entries: Vec<(&DHTKey, &Arc<BucketEntry>)> = inner.buckets[b]
|
||||
.entries()
|
||||
.filter(|e| {
|
||||
let state = e.1.with(inner, |_rti, e| e.state(cur_ts));
|
||||
state >= min_state
|
||||
})
|
||||
.collect();
|
||||
if !filtered_entries.is_empty() {
|
||||
out += &format!(" Bucket #{}:\n", b);
|
||||
for e in filtered_entries {
|
||||
let state = e.1.with(inner, |_rti, e| e.state(cur_ts));
|
||||
out += &format!(
|
||||
" {} [{}]\n",
|
||||
e.0.encode(),
|
||||
match state {
|
||||
BucketEntryState::Reliable => "R",
|
||||
BucketEntryState::Unreliable => "U",
|
||||
BucketEntryState::Dead => "D",
|
||||
}
|
||||
);
|
||||
out += &format!("Entries: {}\n", inner.bucket_entry_count());
|
||||
|
||||
cnt += 1;
|
||||
for ck in &VALID_CRYPTO_KINDS {
|
||||
let blen = inner.buckets[ck].len();
|
||||
while b < blen {
|
||||
let filtered_entries: Vec<(&PublicKey, &Arc<BucketEntry>)> = inner.buckets[ck][b]
|
||||
.entries()
|
||||
.filter(|e| {
|
||||
let state = e.1.with(inner, |_rti, e| e.state(cur_ts));
|
||||
state >= min_state
|
||||
})
|
||||
.collect();
|
||||
if !filtered_entries.is_empty() {
|
||||
out += &format!("{} Bucket #{}:\n", ck, b);
|
||||
for e in filtered_entries {
|
||||
let state = e.1.with(inner, |_rti, e| e.state(cur_ts));
|
||||
out += &format!(
|
||||
" {} [{}]\n",
|
||||
e.0.encode(),
|
||||
match state {
|
||||
BucketEntryState::Reliable => "R",
|
||||
BucketEntryState::Unreliable => "U",
|
||||
BucketEntryState::Dead => "D",
|
||||
}
|
||||
);
|
||||
|
||||
cnt += 1;
|
||||
if cnt >= limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if cnt >= limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if cnt >= limit {
|
||||
break;
|
||||
}
|
||||
b += 1;
|
||||
}
|
||||
b += 1;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn debug_info_entry(&self, node_id: DHTKey) -> String {
|
||||
pub(crate) fn debug_info_entry(&self, node_ref: NodeRef) -> String {
|
||||
let mut out = String::new();
|
||||
out += &format!("Entry {:?}:\n", node_id);
|
||||
if let Some(nr) = self.lookup_node_ref(node_id) {
|
||||
out += &nr.operate(|_rt, e| format!("{:#?}\n", e));
|
||||
} else {
|
||||
out += "Entry not found\n";
|
||||
}
|
||||
|
||||
out += &node_ref.operate(|_rt, e| format!("{:#?}\n", e));
|
||||
out
|
||||
}
|
||||
|
||||
@ -168,26 +172,28 @@ impl RoutingTable {
|
||||
|
||||
let mut out = String::new();
|
||||
const COLS: usize = 16;
|
||||
let rows = inner.buckets.len() / COLS;
|
||||
let mut r = 0;
|
||||
let mut b = 0;
|
||||
out += "Buckets:\n";
|
||||
while r < rows {
|
||||
let mut c = 0;
|
||||
out += format!(" {:>3}: ", b).as_str();
|
||||
while c < COLS {
|
||||
let mut cnt = 0;
|
||||
for e in inner.buckets[b].entries() {
|
||||
if e.1.with(inner, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
cnt += 1;
|
||||
for ck in &VALID_CRYPTO_KINDS {
|
||||
let rows = inner.buckets[ck].len() / COLS;
|
||||
let mut r = 0;
|
||||
let mut b = 0;
|
||||
while r < rows {
|
||||
let mut c = 0;
|
||||
out += format!(" {:>3}: ", b).as_str();
|
||||
while c < COLS {
|
||||
let mut cnt = 0;
|
||||
for e in inner.buckets[ck][b].entries() {
|
||||
if e.1.with(inner, |_rti, e| e.state(cur_ts) >= min_state) {
|
||||
cnt += 1;
|
||||
}
|
||||
}
|
||||
out += format!("{:>3} ", cnt).as_str();
|
||||
b += 1;
|
||||
c += 1;
|
||||
}
|
||||
out += format!("{:>3} ", cnt).as_str();
|
||||
b += 1;
|
||||
c += 1;
|
||||
out += "\n";
|
||||
r += 1;
|
||||
}
|
||||
out += "\n";
|
||||
r += 1;
|
||||
}
|
||||
|
||||
out
|
||||
|
@ -49,7 +49,7 @@ pub struct LowLevelPortInfo {
|
||||
pub protocol_to_port: ProtocolToPortMapping,
|
||||
}
|
||||
pub type RoutingTableEntryFilter<'t> =
|
||||
Box<dyn FnMut(&RoutingTableInner, DHTKey, Option<Arc<BucketEntry>>) -> bool + Send + 't>;
|
||||
Box<dyn FnMut(&RoutingTableInner, Option<Arc<BucketEntry>>) -> bool + Send + 't>;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct RoutingTableHealth {
|
||||
@ -65,17 +65,19 @@ pub struct RoutingTableHealth {
|
||||
pub local_network_ready: bool,
|
||||
}
|
||||
|
||||
pub(super) struct RoutingTableUnlockedInner {
|
||||
pub type BucketIndex = (CryptoKind, usize);
|
||||
|
||||
pub struct RoutingTableUnlockedInner {
|
||||
// Accessors
|
||||
config: VeilidConfig,
|
||||
network_manager: NetworkManager,
|
||||
|
||||
/// The current node's public DHT key
|
||||
node_id: DHTKey,
|
||||
/// The current node's DHT key secret
|
||||
node_id_secret: DHTKeySecret,
|
||||
/// The current node's public DHT keys
|
||||
node_id: TypedKeySet,
|
||||
/// The current node's public DHT secrets
|
||||
node_id_secret: TypedSecretSet,
|
||||
/// Buckets to kick on our next kick task
|
||||
kick_queue: Mutex<BTreeSet<usize>>,
|
||||
kick_queue: Mutex<BTreeSet<BucketIndex>>,
|
||||
/// Background process for computing statistics
|
||||
rolling_transfers_task: TickTask<EyreReport>,
|
||||
/// Background process to purge dead routing table entries when necessary
|
||||
@ -92,6 +94,83 @@ pub(super) struct RoutingTableUnlockedInner {
|
||||
private_route_management_task: TickTask<EyreReport>,
|
||||
}
|
||||
|
||||
impl RoutingTableUnlockedInner {
|
||||
pub fn network_manager(&self) -> NetworkManager {
|
||||
self.network_manager.clone()
|
||||
}
|
||||
pub fn crypto(&self) -> Crypto {
|
||||
self.network_manager().crypto()
|
||||
}
|
||||
pub fn rpc_processor(&self) -> RPCProcessor {
|
||||
self.network_manager().rpc_processor()
|
||||
}
|
||||
pub fn update_callback(&self) -> UpdateCallback {
|
||||
self.network_manager().update_callback()
|
||||
}
|
||||
pub fn with_config<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VeilidConfigInner) -> R,
|
||||
{
|
||||
f(&*self.config.get())
|
||||
}
|
||||
|
||||
pub fn node_id(&self, kind: CryptoKind) -> TypedKey {
|
||||
self.node_id.get(kind).unwrap()
|
||||
}
|
||||
|
||||
pub fn node_id_secret_key(&self, kind: CryptoKind) -> SecretKey {
|
||||
self.node_id_secret.get(kind).unwrap().value
|
||||
}
|
||||
|
||||
pub fn node_ids(&self) -> TypedKeySet {
|
||||
self.node_id.clone()
|
||||
}
|
||||
|
||||
pub fn node_id_typed_key_pairs(&self) -> Vec<TypedKeyPair> {
|
||||
let mut tkps = Vec::new();
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
tkps.push(TypedKeyPair::new(
|
||||
ck,
|
||||
KeyPair::new(self.node_id(ck).value, self.node_id_secret_key(ck)),
|
||||
));
|
||||
}
|
||||
tkps
|
||||
}
|
||||
|
||||
pub fn matches_own_node_id(&self, node_ids: &[TypedKey]) -> bool {
|
||||
for ni in node_ids {
|
||||
if let Some(v) = self.node_id.get(ni.kind) {
|
||||
if v.value == ni.value {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn matches_own_node_id_key(&self, node_id_key: &PublicKey) -> bool {
|
||||
for tk in self.node_id.iter() {
|
||||
if tk.value == *node_id_key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn calculate_bucket_index(&self, node_id: &TypedKey) -> BucketIndex {
|
||||
let crypto = self.crypto();
|
||||
let self_node_id_key = self.node_id(node_id.kind).value;
|
||||
let vcrypto = crypto.get(node_id.kind).unwrap();
|
||||
(
|
||||
node_id.kind,
|
||||
vcrypto
|
||||
.distance(&node_id.value, &self_node_id_key)
|
||||
.first_nonzero_bit()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RoutingTable {
|
||||
inner: Arc<RwLock<RoutingTableInner>>,
|
||||
@ -104,11 +183,12 @@ impl RoutingTable {
|
||||
network_manager: NetworkManager,
|
||||
) -> RoutingTableUnlockedInner {
|
||||
let c = config.get();
|
||||
|
||||
RoutingTableUnlockedInner {
|
||||
config: config.clone(),
|
||||
network_manager,
|
||||
node_id: c.network.node_id.unwrap(),
|
||||
node_id_secret: c.network.node_id_secret.unwrap(),
|
||||
node_id: c.network.routing_table.node_id.clone(),
|
||||
node_id_secret: c.network.routing_table.node_id_secret.clone(),
|
||||
kick_queue: Mutex::new(BTreeSet::default()),
|
||||
rolling_transfers_task: TickTask::new(ROLLING_TRANSFERS_INTERVAL_SECS),
|
||||
kick_buckets_task: TickTask::new(1),
|
||||
@ -133,30 +213,6 @@ impl RoutingTable {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn network_manager(&self) -> NetworkManager {
|
||||
self.unlocked_inner.network_manager.clone()
|
||||
}
|
||||
pub fn rpc_processor(&self) -> RPCProcessor {
|
||||
self.network_manager().rpc_processor()
|
||||
}
|
||||
pub fn update_callback(&self) -> UpdateCallback {
|
||||
self.network_manager().update_callback()
|
||||
}
|
||||
pub fn with_config<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VeilidConfigInner) -> R,
|
||||
{
|
||||
f(&*self.unlocked_inner.config.get())
|
||||
}
|
||||
|
||||
pub fn node_id(&self) -> DHTKey {
|
||||
self.unlocked_inner.node_id
|
||||
}
|
||||
|
||||
pub fn node_id_secret(&self) -> DHTKeySecret {
|
||||
self.unlocked_inner.node_id_secret
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
/// Initialization
|
||||
|
||||
@ -167,7 +223,7 @@ impl RoutingTable {
|
||||
// Set up routing buckets
|
||||
{
|
||||
let mut inner = self.inner.write();
|
||||
inner.init_buckets(self.clone());
|
||||
inner.init_buckets();
|
||||
}
|
||||
|
||||
// Load bucket entries from table db if possible
|
||||
@ -175,7 +231,7 @@ impl RoutingTable {
|
||||
if let Err(e) = self.load_buckets().await {
|
||||
log_rtab!(debug "Error loading buckets from storage: {:#?}. Resetting.", e);
|
||||
let mut inner = self.inner.write();
|
||||
inner.init_buckets(self.clone());
|
||||
inner.init_buckets();
|
||||
}
|
||||
|
||||
// Set up routespecstore
|
||||
@ -229,56 +285,99 @@ impl RoutingTable {
|
||||
debug!("finished routing table terminate");
|
||||
}
|
||||
|
||||
/// Serialize routing table to table store
|
||||
async fn save_buckets(&self) -> EyreResult<()> {
|
||||
// Serialize all entries
|
||||
let mut bucketvec: Vec<Vec<u8>> = Vec::new();
|
||||
// Since entries are shared by multiple buckets per cryptokind
|
||||
// we need to get the list of all unique entries when serializing
|
||||
let mut all_entries: Vec<Arc<BucketEntry>> = Vec::new();
|
||||
|
||||
// Serialize all buckets and get map of entries
|
||||
let mut serialized_bucket_map: BTreeMap<CryptoKind, Vec<Vec<u8>>> = BTreeMap::new();
|
||||
{
|
||||
let mut entry_map: HashMap<*const BucketEntry, u32> = HashMap::new();
|
||||
let inner = &*self.inner.read();
|
||||
for bucket in &inner.buckets {
|
||||
bucketvec.push(bucket.save_bucket()?)
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
let buckets = inner.buckets.get(&ck).unwrap();
|
||||
let mut serialized_buckets = Vec::new();
|
||||
for bucket in buckets.iter() {
|
||||
serialized_buckets.push(bucket.save_bucket(&mut all_entries, &mut entry_map)?)
|
||||
}
|
||||
serialized_bucket_map.insert(ck, serialized_buckets);
|
||||
}
|
||||
}
|
||||
let table_store = self.network_manager().table_store();
|
||||
|
||||
// Serialize all the entries
|
||||
let mut all_entry_bytes = Vec::with_capacity(all_entries.len());
|
||||
for entry in all_entries {
|
||||
// Serialize entry
|
||||
let entry_bytes = entry.with_inner(|e| to_rkyv(e))?;
|
||||
all_entry_bytes.push(entry_bytes);
|
||||
}
|
||||
|
||||
let table_store = self.unlocked_inner.network_manager().table_store();
|
||||
let tdb = table_store.open("routing_table", 1).await?;
|
||||
let bucket_count = bucketvec.len();
|
||||
let dbx = tdb.transact();
|
||||
if let Err(e) = dbx.store_rkyv(0, b"bucket_count", &bucket_count) {
|
||||
if let Err(e) = dbx.store_rkyv(0, b"serialized_bucket_map", &serialized_bucket_map) {
|
||||
dbx.rollback();
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
for (n, b) in bucketvec.iter().enumerate() {
|
||||
dbx.store(0, format!("bucket_{}", n).as_bytes(), b)
|
||||
if let Err(e) = dbx.store_rkyv(0, b"all_entry_bytes", &all_entry_bytes) {
|
||||
dbx.rollback();
|
||||
return Err(e);
|
||||
}
|
||||
dbx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserialize routing table from table store
|
||||
async fn load_buckets(&self) -> EyreResult<()> {
|
||||
// Deserialize all entries
|
||||
let tstore = self.network_manager().table_store();
|
||||
// Deserialize bucket map and all entries from the table store
|
||||
let tstore = self.unlocked_inner.network_manager().table_store();
|
||||
let tdb = tstore.open("routing_table", 1).await?;
|
||||
let Some(bucket_count): Option<usize> = tdb.load_rkyv(0, b"bucket_count")? else {
|
||||
log_rtab!(debug "no bucket count in saved routing table");
|
||||
let Some(serialized_bucket_map): Option<BTreeMap<CryptoKind, Vec<Vec<u8>>>> = tdb.load_rkyv(0, b"serialized_bucket_map")? else {
|
||||
log_rtab!(debug "no bucket map in saved routing table");
|
||||
return Ok(());
|
||||
};
|
||||
let inner = &mut *self.inner.write();
|
||||
if bucket_count != inner.buckets.len() {
|
||||
// Must have the same number of buckets
|
||||
warn!("bucket count is different, not loading routing table");
|
||||
let Some(all_entry_bytes): Option<Vec<Vec<u8>>> = tdb.load_rkyv(0, b"all_entry_bytes")? else {
|
||||
log_rtab!(debug "no all_entry_bytes in saved routing table");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Reconstruct all entries
|
||||
let inner = &mut *self.inner.write();
|
||||
|
||||
let mut all_entries: Vec<Arc<BucketEntry>> = Vec::with_capacity(all_entry_bytes.len());
|
||||
for entry_bytes in all_entry_bytes {
|
||||
let entryinner =
|
||||
from_rkyv(entry_bytes).wrap_err("failed to deserialize bucket entry")?;
|
||||
let entry = Arc::new(BucketEntry::new_with_inner(entryinner));
|
||||
|
||||
// Keep strong reference in table
|
||||
all_entries.push(entry.clone());
|
||||
|
||||
// Keep all entries in weak table too
|
||||
inner.all_entries.insert(entry);
|
||||
}
|
||||
let mut bucketdata_vec: Vec<Vec<u8>> = Vec::new();
|
||||
for n in 0..bucket_count {
|
||||
let Some(bucketdata): Option<Vec<u8>> =
|
||||
tdb.load(0, format!("bucket_{}", n).as_bytes())? else {
|
||||
warn!("bucket data not loading, skipping loading routing table");
|
||||
return Ok(());
|
||||
};
|
||||
bucketdata_vec.push(bucketdata);
|
||||
|
||||
// Validate serialized bucket map
|
||||
for (k, v) in &serialized_bucket_map {
|
||||
if !VALID_CRYPTO_KINDS.contains(k) {
|
||||
warn!("crypto kind is not valid, not loading routing table");
|
||||
return Ok(());
|
||||
}
|
||||
if v.len() != PUBLIC_KEY_LENGTH * 8 {
|
||||
warn!("bucket count is different, not loading routing table");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
for (n, bucketdata) in bucketdata_vec.into_iter().enumerate() {
|
||||
inner.buckets[n].load_bucket(bucketdata)?;
|
||||
|
||||
// Recreate buckets
|
||||
for (k, v) in serialized_bucket_map {
|
||||
let buckets = inner.buckets.get_mut(&k).unwrap();
|
||||
|
||||
for n in 0..v.len() {
|
||||
buckets[n].load_bucket(v[n].clone(), &all_entries)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -443,7 +542,7 @@ impl RoutingTable {
|
||||
}
|
||||
|
||||
/// Attempt to empty the routing table
|
||||
/// should only be performed when there are no node_refs (detached)
|
||||
/// May not empty buckets completely if there are existing node_refs
|
||||
pub fn purge_buckets(&self) {
|
||||
self.inner.write().purge_buckets();
|
||||
}
|
||||
@ -453,20 +552,25 @@ impl RoutingTable {
|
||||
self.inner.write().purge_last_connections();
|
||||
}
|
||||
|
||||
fn find_bucket_index(&self, node_id: DHTKey) -> usize {
|
||||
distance(&node_id, &self.unlocked_inner.node_id)
|
||||
.first_nonzero_bit()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_entry_count(
|
||||
&self,
|
||||
routing_domain_set: RoutingDomainSet,
|
||||
min_state: BucketEntryState,
|
||||
crypto_kinds: &[CryptoKind],
|
||||
) -> usize {
|
||||
self.inner
|
||||
.read()
|
||||
.get_entry_count(routing_domain_set, min_state)
|
||||
.get_entry_count(routing_domain_set, min_state, crypto_kinds)
|
||||
}
|
||||
|
||||
pub fn get_entry_count_per_crypto_kind(
|
||||
&self,
|
||||
routing_domain_set: RoutingDomainSet,
|
||||
min_state: BucketEntryState,
|
||||
) -> BTreeMap<CryptoKind, usize> {
|
||||
self.inner
|
||||
.read()
|
||||
.get_entry_count_per_crypto_kind(routing_domain_set, min_state)
|
||||
}
|
||||
|
||||
pub fn get_nodes_needing_ping(
|
||||
@ -484,32 +588,29 @@ impl RoutingTable {
|
||||
inner.get_all_nodes(self.clone(), cur_ts)
|
||||
}
|
||||
|
||||
fn queue_bucket_kick(&self, node_id: DHTKey) {
|
||||
let idx = self.find_bucket_index(node_id);
|
||||
self.unlocked_inner.kick_queue.lock().insert(idx);
|
||||
fn queue_bucket_kicks(&self, node_ids: TypedKeySet) {
|
||||
for node_id in node_ids.iter() {
|
||||
let x = self.unlocked_inner.calculate_bucket_index(node_id);
|
||||
self.unlocked_inner.kick_queue.lock().insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a node reference, possibly creating a bucket entry
|
||||
/// the 'update_func' closure is called on the node, and, if created,
|
||||
/// in a locked fashion as to ensure the bucket entry state is always valid
|
||||
pub fn create_node_ref<F>(&self, node_id: DHTKey, update_func: F) -> Option<NodeRef>
|
||||
where
|
||||
F: FnOnce(&mut RoutingTableInner, &mut BucketEntryInner),
|
||||
{
|
||||
/// Resolve an existing routing table entry using any crypto kind and return a reference to it
|
||||
pub fn lookup_any_node_ref(&self, node_id_key: PublicKey) -> Option<NodeRef> {
|
||||
self.inner
|
||||
.write()
|
||||
.create_node_ref(self.clone(), node_id, update_func)
|
||||
.read()
|
||||
.lookup_any_node_ref(self.clone(), node_id_key)
|
||||
}
|
||||
|
||||
/// Resolve an existing routing table entry and return a reference to it
|
||||
pub fn lookup_node_ref(&self, node_id: DHTKey) -> Option<NodeRef> {
|
||||
pub fn lookup_node_ref(&self, node_id: TypedKey) -> Option<NodeRef> {
|
||||
self.inner.read().lookup_node_ref(self.clone(), node_id)
|
||||
}
|
||||
|
||||
/// Resolve an existing routing table entry and return a filtered reference to it
|
||||
pub fn lookup_and_filter_noderef(
|
||||
&self,
|
||||
node_id: DHTKey,
|
||||
node_id: TypedKey,
|
||||
routing_domain_set: RoutingDomainSet,
|
||||
dial_info_filter: DialInfoFilter,
|
||||
) -> Option<NodeRef> {
|
||||
@ -524,18 +625,16 @@ impl RoutingTable {
|
||||
/// Shortcut function to add a node to our routing table if it doesn't exist
|
||||
/// and add the dial info we have for it. Returns a noderef filtered to
|
||||
/// the routing domain in which this node was registered for convenience.
|
||||
pub fn register_node_with_signed_node_info(
|
||||
pub fn register_node_with_peer_info(
|
||||
&self,
|
||||
routing_domain: RoutingDomain,
|
||||
node_id: DHTKey,
|
||||
signed_node_info: SignedNodeInfo,
|
||||
peer_info: PeerInfo,
|
||||
allow_invalid: bool,
|
||||
) -> Option<NodeRef> {
|
||||
self.inner.write().register_node_with_signed_node_info(
|
||||
self.inner.write().register_node_with_peer_info(
|
||||
self.clone(),
|
||||
routing_domain,
|
||||
node_id,
|
||||
signed_node_info,
|
||||
peer_info,
|
||||
allow_invalid,
|
||||
)
|
||||
}
|
||||
@ -544,7 +643,7 @@ impl RoutingTable {
|
||||
/// and add the last peer address we have for it, since that's pretty common
|
||||
pub fn register_node_with_existing_connection(
|
||||
&self,
|
||||
node_id: DHTKey,
|
||||
node_id: TypedKey,
|
||||
descriptor: ConnectionDescriptor,
|
||||
timestamp: Timestamp,
|
||||
) -> Option<NodeRef> {
|
||||
@ -563,7 +662,7 @@ impl RoutingTable {
|
||||
self.inner.read().get_routing_table_health()
|
||||
}
|
||||
|
||||
pub fn get_recent_peers(&self) -> Vec<(DHTKey, RecentPeersEntry)> {
|
||||
pub fn get_recent_peers(&self) -> Vec<(TypedKey, RecentPeersEntry)> {
|
||||
let mut recent_peers = Vec::new();
|
||||
let mut dead_peers = Vec::new();
|
||||
let mut out = Vec::new();
|
||||
@ -602,7 +701,7 @@ impl RoutingTable {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn touch_recent_peer(&self, node_id: DHTKey, last_connection: ConnectionDescriptor) {
|
||||
pub fn touch_recent_peer(&self, node_id: TypedKey, last_connection: ConnectionDescriptor) {
|
||||
self.inner
|
||||
.write()
|
||||
.touch_recent_peer(node_id, last_connection)
|
||||
@ -651,7 +750,7 @@ impl RoutingTable {
|
||||
dial_info_filter: DialInfoFilter,
|
||||
) -> RoutingTableEntryFilter<'a> {
|
||||
// does it have matching public dial info?
|
||||
Box::new(move |rti, _k, e| {
|
||||
Box::new(move |rti, e| {
|
||||
if let Some(e) = e {
|
||||
e.with(rti, |_rti, e| {
|
||||
if let Some(ni) = e.node_info(routing_domain) {
|
||||
@ -679,7 +778,7 @@ impl RoutingTable {
|
||||
dial_info: DialInfo,
|
||||
) -> RoutingTableEntryFilter<'a> {
|
||||
// does the node's outbound capabilities match the dialinfo?
|
||||
Box::new(move |rti, _k, e| {
|
||||
Box::new(move |rti, e| {
|
||||
if let Some(e) = e {
|
||||
e.with(rti, |_rti, e| {
|
||||
if let Some(ni) = e.node_info(routing_domain) {
|
||||
@ -709,27 +808,37 @@ impl RoutingTable {
|
||||
.find_fast_public_nodes_filtered(self.clone(), node_count, filters)
|
||||
}
|
||||
|
||||
/// Retrieve up to N of each type of protocol capable nodes
|
||||
pub fn find_bootstrap_nodes_filtered(&self, max_per_type: usize) -> Vec<NodeRef> {
|
||||
/// Retrieve up to N of each type of protocol capable nodes for a single crypto kind
|
||||
fn find_bootstrap_nodes_filtered_per_crypto_kind(
|
||||
&self,
|
||||
crypto_kind: CryptoKind,
|
||||
max_per_type: usize,
|
||||
) -> Vec<NodeRef> {
|
||||
let protocol_types = vec![
|
||||
ProtocolType::UDP,
|
||||
ProtocolType::TCP,
|
||||
ProtocolType::WS,
|
||||
ProtocolType::WSS,
|
||||
];
|
||||
|
||||
let protocol_types_len = protocol_types.len();
|
||||
let mut nodes_proto_v4 = vec![0usize, 0usize, 0usize, 0usize];
|
||||
let mut nodes_proto_v6 = vec![0usize, 0usize, 0usize, 0usize];
|
||||
|
||||
let filter = Box::new(
|
||||
move |rti: &RoutingTableInner, _k: DHTKey, v: Option<Arc<BucketEntry>>| {
|
||||
let entry = v.unwrap();
|
||||
move |rti: &RoutingTableInner, entry: Option<Arc<BucketEntry>>| {
|
||||
let entry = entry.unwrap();
|
||||
entry.with(rti, |_rti, e| {
|
||||
// skip nodes on our local network here
|
||||
if e.has_node_info(RoutingDomain::LocalNetwork.into()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure crypto kind is supported
|
||||
if !e.crypto_kinds().contains(&crypto_kind) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// does it have some dial info we need?
|
||||
let filter = |n: &NodeInfo| {
|
||||
let mut keep = false;
|
||||
@ -769,12 +878,33 @@ impl RoutingTable {
|
||||
self.find_fastest_nodes(
|
||||
protocol_types_len * 2 * max_per_type,
|
||||
filters,
|
||||
|_rti, k: DHTKey, v: Option<Arc<BucketEntry>>| {
|
||||
NodeRef::new(self.clone(), k, v.unwrap().clone(), None)
|
||||
|_rti, entry: Option<Arc<BucketEntry>>| {
|
||||
NodeRef::new(self.clone(), entry.unwrap().clone(), None)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieve up to N of each type of protocol capable nodes for all crypto kinds
|
||||
pub fn find_bootstrap_nodes_filtered(&self, max_per_type: usize) -> Vec<NodeRef> {
|
||||
let mut out =
|
||||
self.find_bootstrap_nodes_filtered_per_crypto_kind(VALID_CRYPTO_KINDS[0], max_per_type);
|
||||
|
||||
// Merge list of nodes so we don't have duplicates
|
||||
for crypto_kind in &VALID_CRYPTO_KINDS[1..] {
|
||||
let nrs =
|
||||
self.find_bootstrap_nodes_filtered_per_crypto_kind(*crypto_kind, max_per_type);
|
||||
'nrloop: for nr in nrs {
|
||||
for nro in &out {
|
||||
if nro.same_entry(&nr) {
|
||||
continue 'nrloop;
|
||||
}
|
||||
}
|
||||
out.push(nr);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn find_peers_with_sort_and_filter<C, T, O>(
|
||||
&self,
|
||||
node_count: usize,
|
||||
@ -786,10 +916,10 @@ impl RoutingTable {
|
||||
where
|
||||
C: for<'a, 'b> FnMut(
|
||||
&'a RoutingTableInner,
|
||||
&'b (DHTKey, Option<Arc<BucketEntry>>),
|
||||
&'b (DHTKey, Option<Arc<BucketEntry>>),
|
||||
&'b Option<Arc<BucketEntry>>,
|
||||
&'b Option<Arc<BucketEntry>>,
|
||||
) -> core::cmp::Ordering,
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, DHTKey, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
{
|
||||
self.inner
|
||||
.read()
|
||||
@ -803,7 +933,7 @@ impl RoutingTable {
|
||||
transform: T,
|
||||
) -> Vec<O>
|
||||
where
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, DHTKey, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
{
|
||||
self.inner
|
||||
.read()
|
||||
@ -812,44 +942,42 @@ impl RoutingTable {
|
||||
|
||||
pub fn find_closest_nodes<'a, T, O>(
|
||||
&self,
|
||||
node_id: DHTKey,
|
||||
node_count: usize,
|
||||
node_id: TypedKey,
|
||||
filters: VecDeque<RoutingTableEntryFilter>,
|
||||
transform: T,
|
||||
) -> Vec<O>
|
||||
where
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, DHTKey, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
T: for<'r> FnMut(&'r RoutingTableInner, Option<Arc<BucketEntry>>) -> O + Send,
|
||||
{
|
||||
self.inner
|
||||
.read()
|
||||
.find_closest_nodes(node_id, filters, transform)
|
||||
.find_closest_nodes(node_count, node_id, filters, transform)
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self), ret)]
|
||||
pub fn register_find_node_answer(&self, peers: Vec<PeerInfo>) -> Vec<NodeRef> {
|
||||
let node_id = self.node_id();
|
||||
|
||||
// register nodes we'd found
|
||||
pub fn register_find_node_answer(
|
||||
&self,
|
||||
crypto_kind: CryptoKind,
|
||||
peers: Vec<PeerInfo>,
|
||||
) -> Vec<NodeRef> {
|
||||
// Register nodes we'd found
|
||||
let mut out = Vec::<NodeRef>::with_capacity(peers.len());
|
||||
for p in peers {
|
||||
// if our own node if is in the list then ignore it, as we don't add ourselves to our own routing table
|
||||
if p.node_id.key == node_id {
|
||||
// Ensure we're getting back nodes we asked for
|
||||
if !p.node_ids.kinds().contains(&crypto_kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// node can not be its own relay
|
||||
if let Some(rid) = &p.signed_node_info.relay_id() {
|
||||
if rid.key == p.node_id.key {
|
||||
continue;
|
||||
}
|
||||
// Don't register our own node
|
||||
if self.matches_own_node_id(&p.node_ids) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// register the node if it's new
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
p.node_id.key,
|
||||
p.signed_node_info.clone(),
|
||||
false,
|
||||
) {
|
||||
// Register the node if it's new
|
||||
if let Some(nr) =
|
||||
self.register_node_with_peer_info(RoutingDomain::PublicInternet, p, false)
|
||||
{
|
||||
out.push(nr);
|
||||
}
|
||||
}
|
||||
@ -860,7 +988,7 @@ impl RoutingTable {
|
||||
pub async fn find_node(
|
||||
&self,
|
||||
node_ref: NodeRef,
|
||||
node_id: DHTKey,
|
||||
node_id: TypedKey,
|
||||
) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let rpc_processor = self.rpc_processor();
|
||||
|
||||
@ -873,29 +1001,41 @@ impl RoutingTable {
|
||||
|
||||
// register nodes we'd found
|
||||
Ok(NetworkResult::value(
|
||||
self.register_find_node_answer(res.answer),
|
||||
self.register_find_node_answer(node_id.kind, res.answer),
|
||||
))
|
||||
}
|
||||
|
||||
/// Ask a remote node to list the nodes it has around the current node
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn find_self(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let node_id = self.node_id();
|
||||
self.find_node(node_ref, node_id).await
|
||||
pub async fn find_self(
|
||||
&self,
|
||||
crypto_kind: CryptoKind,
|
||||
node_ref: NodeRef,
|
||||
) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let self_node_id = self.node_id(crypto_kind);
|
||||
self.find_node(node_ref, self_node_id).await
|
||||
}
|
||||
|
||||
/// Ask a remote node to list the nodes it has around itself
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub async fn find_target(&self, node_ref: NodeRef) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let node_id = node_ref.node_id();
|
||||
self.find_node(node_ref, node_id).await
|
||||
pub async fn find_target(
|
||||
&self,
|
||||
crypto_kind: CryptoKind,
|
||||
node_ref: NodeRef,
|
||||
) -> EyreResult<NetworkResult<Vec<NodeRef>>> {
|
||||
let Some(target_node_id) = node_ref.node_ids().get(crypto_kind) else {
|
||||
bail!("no target node ids for this crypto kind");
|
||||
};
|
||||
self.find_node(node_ref, target_node_id).await
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
pub async fn reverse_find_node(&self, node_ref: NodeRef, wide: bool) {
|
||||
// Ask bootstrap node to 'find' our own node so we can get some more nodes near ourselves
|
||||
pub async fn reverse_find_node(&self, crypto_kind: CryptoKind, node_ref: NodeRef, wide: bool) {
|
||||
// Ask node to 'find node' on own node so we can get some more nodes near ourselves
|
||||
// and then contact those nodes to inform -them- that we exist
|
||||
|
||||
// Ask bootstrap server for nodes closest to our own node
|
||||
let closest_nodes = network_result_value_or_log!(match self.find_self(node_ref.clone()).await {
|
||||
// Ask node for nodes closest to our own node
|
||||
let closest_nodes = network_result_value_or_log!(match self.find_self(crypto_kind, node_ref.clone()).await {
|
||||
Err(e) => {
|
||||
log_rtab!(error
|
||||
"find_self failed for {:?}: {:?}",
|
||||
@ -911,7 +1051,7 @@ impl RoutingTable {
|
||||
// Ask each node near us to find us as well
|
||||
if wide {
|
||||
for closest_nr in closest_nodes {
|
||||
network_result_value_or_log!(match self.find_self(closest_nr.clone()).await {
|
||||
network_result_value_or_log!(match self.find_self(crypto_kind, closest_nr.clone()).await {
|
||||
Err(e) => {
|
||||
log_rtab!(error
|
||||
"find_self failed for {:?}: {:?}",
|
||||
@ -986,12 +1126,12 @@ impl RoutingTable {
|
||||
// Go through all entries and find fastest entry that matches filter function
|
||||
let inner = self.inner.read();
|
||||
let inner = &*inner;
|
||||
let mut best_inbound_relay: Option<(DHTKey, Arc<BucketEntry>)> = None;
|
||||
let mut best_inbound_relay: Option<Arc<BucketEntry>> = None;
|
||||
|
||||
// Iterate all known nodes for candidates
|
||||
inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| {
|
||||
let v2 = v.clone();
|
||||
v.with(rti, |rti, e| {
|
||||
inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| {
|
||||
let entry2 = entry.clone();
|
||||
entry.with(rti, |rti, e| {
|
||||
// Ensure we have the node's status
|
||||
if let Some(node_status) = e.node_status(routing_domain) {
|
||||
// Ensure the node will relay
|
||||
@ -999,18 +1139,18 @@ impl RoutingTable {
|
||||
// Compare against previous candidate
|
||||
if let Some(best_inbound_relay) = best_inbound_relay.as_mut() {
|
||||
// Less is faster
|
||||
let better = best_inbound_relay.1.with(rti, |_rti, best| {
|
||||
let better = best_inbound_relay.with(rti, |_rti, best| {
|
||||
// choose low latency stability for relays
|
||||
BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best)
|
||||
== std::cmp::Ordering::Less
|
||||
});
|
||||
// Now apply filter function and see if this node should be included
|
||||
if better && relay_node_filter(e) {
|
||||
*best_inbound_relay = (k, v2);
|
||||
*best_inbound_relay = entry2;
|
||||
}
|
||||
} else if relay_node_filter(e) {
|
||||
// Always store the first candidate
|
||||
best_inbound_relay = Some((k, v2));
|
||||
best_inbound_relay = Some(entry2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1019,6 +1159,14 @@ impl RoutingTable {
|
||||
Option::<()>::None
|
||||
});
|
||||
// Return the best inbound relay noderef
|
||||
best_inbound_relay.map(|(k, e)| NodeRef::new(self.clone(), k, e, None))
|
||||
best_inbound_relay.map(|e| NodeRef::new(self.clone(), e, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for RoutingTable {
|
||||
type Target = RoutingTableUnlockedInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.unlocked_inner
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use alloc::fmt;
|
||||
|
||||
pub struct NodeRefBaseCommon {
|
||||
routing_table: RoutingTable,
|
||||
node_id: DHTKey,
|
||||
entry: Arc<BucketEntry>,
|
||||
filter: Option<NodeRefFilter>,
|
||||
sequencing: Sequencing,
|
||||
@ -21,6 +20,14 @@ pub trait NodeRefBase: Sized {
|
||||
fn common(&self) -> &NodeRefBaseCommon;
|
||||
fn common_mut(&mut self) -> &mut NodeRefBaseCommon;
|
||||
|
||||
// Comparators
|
||||
fn same_entry<T: NodeRefBase>(&self, other: &T) -> bool {
|
||||
Arc::ptr_eq(&self.common().entry, &other.common().entry)
|
||||
}
|
||||
fn same_bucket_entry(&self, entry: &Arc<BucketEntry>) -> bool {
|
||||
Arc::ptr_eq(&self.common().entry, entry)
|
||||
}
|
||||
|
||||
// Implementation-specific operators
|
||||
fn operate<T, F>(&self, f: F) -> T
|
||||
where
|
||||
@ -99,8 +106,11 @@ pub trait NodeRefBase: Sized {
|
||||
fn routing_table(&self) -> RoutingTable {
|
||||
self.common().routing_table.clone()
|
||||
}
|
||||
fn node_id(&self) -> DHTKey {
|
||||
self.common().node_id
|
||||
fn node_ids(&self) -> TypedKeySet {
|
||||
self.operate(|_rti, e| e.node_ids())
|
||||
}
|
||||
fn best_node_id(&self) -> TypedKey {
|
||||
self.operate(|_rti, e| e.best_node_id())
|
||||
}
|
||||
fn has_updated_since_last_network_change(&self) -> bool {
|
||||
self.operate(|_rti, e| e.has_updated_since_last_network_change())
|
||||
@ -113,11 +123,17 @@ pub trait NodeRefBase: Sized {
|
||||
e.update_node_status(node_status);
|
||||
});
|
||||
}
|
||||
fn min_max_version(&self) -> Option<VersionRange> {
|
||||
self.operate(|_rti, e| e.min_max_version())
|
||||
fn envelope_support(&self) -> Vec<u8> {
|
||||
self.operate(|_rti, e| e.envelope_support())
|
||||
}
|
||||
fn set_min_max_version(&self, min_max_version: VersionRange) {
|
||||
self.operate_mut(|_rti, e| e.set_min_max_version(min_max_version))
|
||||
fn add_envelope_version(&self, envelope_version: u8) {
|
||||
self.operate_mut(|_rti, e| e.add_envelope_version(envelope_version))
|
||||
}
|
||||
fn set_envelope_support(&self, envelope_support: Vec<u8>) {
|
||||
self.operate_mut(|_rti, e| e.set_envelope_support(envelope_support))
|
||||
}
|
||||
fn best_envelope_version(&self) -> Option<u8> {
|
||||
self.operate(|_rti, e| e.best_envelope_version())
|
||||
}
|
||||
fn state(&self, cur_ts: Timestamp) -> BucketEntryState {
|
||||
self.operate(|_rti, e| e.state(cur_ts))
|
||||
@ -128,7 +144,7 @@ pub trait NodeRefBase: Sized {
|
||||
|
||||
// Per-RoutingDomain accessors
|
||||
fn make_peer_info(&self, routing_domain: RoutingDomain) -> Option<PeerInfo> {
|
||||
self.operate(|_rti, e| e.make_peer_info(self.node_id(), routing_domain))
|
||||
self.operate(|_rti, e| e.make_peer_info(routing_domain))
|
||||
}
|
||||
fn node_info(&self, routing_domain: RoutingDomain) -> Option<NodeInfo> {
|
||||
self.operate(|_rti, e| e.node_info(routing_domain).cloned())
|
||||
@ -136,7 +152,7 @@ pub trait NodeRefBase: Sized {
|
||||
fn signed_node_info_has_valid_signature(&self, routing_domain: RoutingDomain) -> bool {
|
||||
self.operate(|_rti, e| {
|
||||
e.signed_node_info(routing_domain)
|
||||
.map(|sni| sni.has_valid_signature())
|
||||
.map(|sni| sni.has_any_signature())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
@ -180,19 +196,18 @@ pub trait NodeRefBase: Sized {
|
||||
self.operate_mut(|rti, e| {
|
||||
e.signed_node_info(routing_domain)
|
||||
.and_then(|n| n.relay_peer_info())
|
||||
.and_then(|t| {
|
||||
.and_then(|rpi| {
|
||||
// If relay is ourselves, then return None, because we can't relay through ourselves
|
||||
// and to contact this node we should have had an existing inbound connection
|
||||
if t.node_id.key == rti.unlocked_inner.node_id {
|
||||
if rti.unlocked_inner.matches_own_node_id(&rpi.node_ids) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Register relay node and return noderef
|
||||
rti.register_node_with_signed_node_info(
|
||||
rti.register_node_with_peer_info(
|
||||
self.routing_table(),
|
||||
routing_domain,
|
||||
t.node_id.key,
|
||||
t.signed_node_info,
|
||||
rpi,
|
||||
false,
|
||||
)
|
||||
})
|
||||
@ -280,7 +295,7 @@ pub trait NodeRefBase: Sized {
|
||||
fn set_last_connection(&self, connection_descriptor: ConnectionDescriptor, ts: Timestamp) {
|
||||
self.operate_mut(|rti, e| {
|
||||
e.set_last_connection(connection_descriptor, ts);
|
||||
rti.touch_recent_peer(self.common().node_id, connection_descriptor);
|
||||
rti.touch_recent_peer(e.best_node_id(), connection_descriptor);
|
||||
})
|
||||
}
|
||||
|
||||
@ -346,7 +361,6 @@ pub struct NodeRef {
|
||||
impl NodeRef {
|
||||
pub fn new(
|
||||
routing_table: RoutingTable,
|
||||
node_id: DHTKey,
|
||||
entry: Arc<BucketEntry>,
|
||||
filter: Option<NodeRefFilter>,
|
||||
) -> Self {
|
||||
@ -355,7 +369,6 @@ impl NodeRef {
|
||||
Self {
|
||||
common: NodeRefBaseCommon {
|
||||
routing_table,
|
||||
node_id,
|
||||
entry,
|
||||
filter,
|
||||
sequencing: Sequencing::NoPreference,
|
||||
@ -415,7 +428,6 @@ impl Clone for NodeRef {
|
||||
Self {
|
||||
common: NodeRefBaseCommon {
|
||||
routing_table: self.common.routing_table.clone(),
|
||||
node_id: self.common.node_id,
|
||||
entry: self.common.entry.clone(),
|
||||
filter: self.common.filter.clone(),
|
||||
sequencing: self.common.sequencing,
|
||||
@ -428,14 +440,14 @@ impl Clone for NodeRef {
|
||||
|
||||
impl fmt::Display for NodeRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.common.node_id.encode())
|
||||
write!(f, "{}", self.common.entry.with_inner(|e| e.best_node_id()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NodeRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NodeRef")
|
||||
.field("node_id", &self.common.node_id)
|
||||
.field("node_ids", &self.common.entry.with_inner(|e| e.node_ids()))
|
||||
.field("filter", &self.common.filter)
|
||||
.field("sequencing", &self.common.sequencing)
|
||||
.finish()
|
||||
@ -455,9 +467,10 @@ impl Drop for NodeRef {
|
||||
.fetch_sub(1u32, Ordering::Relaxed)
|
||||
- 1;
|
||||
if new_ref_count == 0 {
|
||||
self.common
|
||||
.routing_table
|
||||
.queue_bucket_kick(self.common.node_id);
|
||||
// get node ids with inner unlocked because nothing could be referencing this entry now
|
||||
// and we don't know when it will get dropped, possibly inside a lock
|
||||
let node_ids = self.common().entry.with_inner(|e| e.node_ids());
|
||||
self.common.routing_table.queue_bucket_kicks(node_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -480,6 +493,10 @@ impl<'a> NodeRefLocked<'a> {
|
||||
nr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlocked(&self) -> NodeRef {
|
||||
self.nr.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NodeRefBase for NodeRefLocked<'a> {
|
||||
@ -539,6 +556,10 @@ impl<'a> NodeRefLockedMut<'a> {
|
||||
nr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlocked(&self) -> NodeRef {
|
||||
self.nr.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NodeRefBase for NodeRefLockedMut<'a> {
|
||||
|
@ -16,20 +16,45 @@ pub struct RouteHopData {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RouteNode {
|
||||
/// Route node is optimized, no contact method information as this node id has been seen before
|
||||
NodeId(NodeId),
|
||||
NodeId(PublicKey),
|
||||
/// Route node with full contact method information to ensure the peer is reachable
|
||||
PeerInfo(PeerInfo),
|
||||
}
|
||||
impl fmt::Display for RouteNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
RouteNode::NodeId(x) => x.key.encode(),
|
||||
RouteNode::PeerInfo(pi) => pi.node_id.key.encode(),
|
||||
|
||||
impl RouteNode {
|
||||
pub fn node_ref(
|
||||
&self,
|
||||
routing_table: RoutingTable,
|
||||
crypto_kind: CryptoKind,
|
||||
) -> Option<NodeRef> {
|
||||
match self {
|
||||
RouteNode::NodeId(id) => {
|
||||
//
|
||||
routing_table.lookup_node_ref(TypedKey::new(crypto_kind, *id))
|
||||
}
|
||||
)
|
||||
RouteNode::PeerInfo(pi) => {
|
||||
//
|
||||
routing_table.register_node_with_peer_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
pi.clone(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn describe(&self, crypto_kind: CryptoKind) -> String {
|
||||
match self {
|
||||
RouteNode::NodeId(id) => {
|
||||
format!("{}", TypedKey::new(crypto_kind, *id))
|
||||
}
|
||||
RouteNode::PeerInfo(pi) => match pi.node_ids.get(crypto_kind) {
|
||||
Some(id) => format!("{}", id),
|
||||
None => {
|
||||
format!("({})?{}", crypto_kind, pi.node_ids)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,14 +82,14 @@ pub enum PrivateRouteHops {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrivateRoute {
|
||||
/// The public key used for the entire route
|
||||
pub public_key: DHTKey,
|
||||
pub public_key: TypedKey,
|
||||
pub hop_count: u8,
|
||||
pub hops: PrivateRouteHops,
|
||||
}
|
||||
|
||||
impl PrivateRoute {
|
||||
/// Empty private route is the form used when receiving the last hop
|
||||
pub fn new_empty(public_key: DHTKey) -> Self {
|
||||
pub fn new_empty(public_key: TypedKey) -> Self {
|
||||
Self {
|
||||
public_key,
|
||||
hop_count: 0,
|
||||
@ -72,7 +97,7 @@ impl PrivateRoute {
|
||||
}
|
||||
}
|
||||
/// Stub route is the form used when no privacy is required, but you need to specify the destination for a safety route
|
||||
pub fn new_stub(public_key: DHTKey, node: RouteNode) -> Self {
|
||||
pub fn new_stub(public_key: TypedKey, node: RouteNode) -> Self {
|
||||
Self {
|
||||
public_key,
|
||||
hop_count: 1,
|
||||
@ -91,6 +116,11 @@ impl PrivateRoute {
|
||||
false
|
||||
}
|
||||
|
||||
/// Get the crypto kind in use for this route
|
||||
pub fn crypto_kind(&self) -> CryptoKind {
|
||||
self.public_key.kind
|
||||
}
|
||||
|
||||
/// Remove the first unencrypted hop if possible
|
||||
pub fn pop_first_hop(&mut self) -> Option<RouteNode> {
|
||||
match &mut self.hops {
|
||||
@ -117,15 +147,15 @@ impl PrivateRoute {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_hop_node_id(&self) -> Option<DHTKey> {
|
||||
pub fn first_hop_node_id(&self) -> Option<TypedKey> {
|
||||
let PrivateRouteHops::FirstHop(pr_first_hop) = &self.hops else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Get the safety route to use from the spec
|
||||
Some(match &pr_first_hop.node {
|
||||
RouteNode::NodeId(n) => n.key,
|
||||
RouteNode::PeerInfo(p) => p.node_id.key,
|
||||
RouteNode::NodeId(n) => TypedKey::new(self.public_key.kind, *n),
|
||||
RouteNode::PeerInfo(p) => p.node_ids.get(self.public_key.kind).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -138,8 +168,13 @@ impl fmt::Display for PrivateRoute {
|
||||
self.public_key,
|
||||
self.hop_count,
|
||||
match &self.hops {
|
||||
PrivateRouteHops::FirstHop(fh) => {
|
||||
format!("->{}", fh.node)
|
||||
PrivateRouteHops::FirstHop(_) => {
|
||||
format!(
|
||||
"->{}",
|
||||
self.first_hop_node_id()
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_else(|| "None".to_owned())
|
||||
)
|
||||
}
|
||||
PrivateRouteHops::Data(_) => {
|
||||
"->?".to_owned()
|
||||
@ -162,13 +197,14 @@ pub enum SafetyRouteHops {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SafetyRoute {
|
||||
pub public_key: DHTKey,
|
||||
pub public_key: TypedKey,
|
||||
pub hop_count: u8,
|
||||
pub hops: SafetyRouteHops,
|
||||
}
|
||||
|
||||
impl SafetyRoute {
|
||||
pub fn new_stub(public_key: DHTKey, private_route: PrivateRoute) -> Self {
|
||||
/// Stub route is the form used when no privacy is required, but you need to directly contact a private route
|
||||
pub fn new_stub(public_key: TypedKey, private_route: PrivateRoute) -> Self {
|
||||
// First hop should have already been popped off for stubbed safety routes since
|
||||
// we are sending directly to the first hop
|
||||
assert!(matches!(private_route.hops, PrivateRouteHops::Data(_)));
|
||||
@ -178,9 +214,16 @@ impl SafetyRoute {
|
||||
hops: SafetyRouteHops::Private(private_route),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this is a stub route
|
||||
pub fn is_stub(&self) -> bool {
|
||||
matches!(self.hops, SafetyRouteHops::Private(_))
|
||||
}
|
||||
|
||||
/// Get the crypto kind in use for this route
|
||||
pub fn crypto_kind(&self) -> CryptoKind {
|
||||
self.public_key.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SafetyRoute {
|
||||
|
File diff suppressed because it is too large
Load Diff
30
veilid-core/src/routing_table/route_spec_store/mod.rs
Normal file
30
veilid-core/src/routing_table/route_spec_store/mod.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use super::*;
|
||||
|
||||
mod permutation;
|
||||
mod remote_private_route_info;
|
||||
mod route_set_spec_detail;
|
||||
mod route_spec_store;
|
||||
mod route_spec_store_cache;
|
||||
mod route_spec_store_content;
|
||||
mod route_stats;
|
||||
|
||||
pub use remote_private_route_info::*;
|
||||
pub use route_set_spec_detail::*;
|
||||
pub use route_spec_store::*;
|
||||
pub use route_spec_store_cache::*;
|
||||
pub use route_spec_store_content::*;
|
||||
pub use route_stats::*;
|
||||
|
||||
use crate::veilid_api::*;
|
||||
use rkyv::{
|
||||
with::Skip, Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize,
|
||||
};
|
||||
|
||||
/// The size of the remote private route cache
|
||||
const REMOTE_PRIVATE_ROUTE_CACHE_SIZE: usize = 1024;
|
||||
/// Remote private route cache entries expire in 5 minutes if they haven't been used
|
||||
const REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY: TimestampDuration = TimestampDuration::new(300_000_000u64);
|
||||
/// Amount of time a route can remain idle before it gets tested
|
||||
const ROUTE_MIN_IDLE_TIME_MS: u32 = 30_000;
|
||||
/// The size of the compiled route cache
|
||||
const COMPILED_ROUTE_CACHE_SIZE: usize = 256;
|
@ -0,0 +1,70 @@
|
||||
use super::*;
|
||||
|
||||
/// number of route permutations is the number of unique orderings
|
||||
/// for a set of nodes, given that the first node is fixed
|
||||
fn _get_route_permutation_count(hop_count: usize) -> usize {
|
||||
if hop_count == 0 {
|
||||
unreachable!();
|
||||
}
|
||||
// a single node or two nodes is always fixed
|
||||
if hop_count == 1 || hop_count == 2 {
|
||||
return 1;
|
||||
}
|
||||
// more than two nodes has factorial permutation
|
||||
// hop_count = 3 -> 2! -> 2
|
||||
// hop_count = 4 -> 3! -> 6
|
||||
(3..hop_count).into_iter().fold(2usize, |acc, x| acc * x)
|
||||
}
|
||||
pub type PermReturnType = (Vec<usize>, bool);
|
||||
pub type PermFunc<'t> = Box<dyn FnMut(&[usize]) -> Option<PermReturnType> + Send + 't>;
|
||||
|
||||
/// get the route permutation at particular 'perm' index, starting at the 'start' index
|
||||
/// for a set of 'hop_count' nodes. the first node is always fixed, and the maximum
|
||||
/// number of permutations is given by get_route_permutation_count()
|
||||
|
||||
pub fn with_route_permutations(
|
||||
hop_count: usize,
|
||||
start: usize,
|
||||
f: &mut PermFunc,
|
||||
) -> Option<PermReturnType> {
|
||||
if hop_count == 0 {
|
||||
unreachable!();
|
||||
}
|
||||
// initial permutation
|
||||
let mut permutation: Vec<usize> = Vec::with_capacity(hop_count);
|
||||
for n in 0..hop_count {
|
||||
permutation.push(start + n);
|
||||
}
|
||||
// if we have one hop or two, then there's only one permutation
|
||||
if hop_count == 1 || hop_count == 2 {
|
||||
return f(&permutation);
|
||||
}
|
||||
|
||||
// heaps algorithm, but skipping the first element
|
||||
fn heaps_permutation(
|
||||
permutation: &mut [usize],
|
||||
size: usize,
|
||||
f: &mut PermFunc,
|
||||
) -> Option<PermReturnType> {
|
||||
if size == 1 {
|
||||
return f(&permutation);
|
||||
}
|
||||
|
||||
for i in 0..size {
|
||||
let out = heaps_permutation(permutation, size - 1, f);
|
||||
if out.is_some() {
|
||||
return out;
|
||||
}
|
||||
if size % 2 == 1 {
|
||||
permutation.swap(1, size);
|
||||
} else {
|
||||
permutation.swap(1 + i, size);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// recurse
|
||||
heaps_permutation(&mut permutation, hop_count - 1, f)
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
use super::*;
|
||||
|
||||
/// What remote private routes have seen
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RemotePrivateRouteInfo {
|
||||
/// The private routes themselves
|
||||
private_routes: Vec<PrivateRoute>,
|
||||
/// Did this remote private route see our node info due to no safety route in use
|
||||
last_seen_our_node_info_ts: Timestamp,
|
||||
/// Last time this remote private route was requested for any reason (cache expiration)
|
||||
last_touched_ts: Timestamp,
|
||||
/// Stats
|
||||
stats: RouteStats,
|
||||
}
|
||||
|
||||
impl RemotePrivateRouteInfo {
|
||||
pub fn new(private_routes: Vec<PrivateRoute>, cur_ts: Timestamp) -> Self {
|
||||
RemotePrivateRouteInfo {
|
||||
private_routes,
|
||||
last_seen_our_node_info_ts: Timestamp::new(0),
|
||||
last_touched_ts: cur_ts,
|
||||
stats: RouteStats::new(cur_ts),
|
||||
}
|
||||
}
|
||||
pub fn get_private_routes(&self) -> &[PrivateRoute] {
|
||||
&self.private_routes
|
||||
}
|
||||
pub fn best_private_route(&self) -> Option<PrivateRoute> {
|
||||
self.private_routes
|
||||
.iter()
|
||||
.reduce(|acc, x| {
|
||||
if x.public_key < acc.public_key {
|
||||
x
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
})
|
||||
.filter(|x| VALID_CRYPTO_KINDS.contains(&x.public_key.kind))
|
||||
.cloned()
|
||||
}
|
||||
pub fn get_stats(&self) -> &RouteStats {
|
||||
&self.stats
|
||||
}
|
||||
pub fn get_stats_mut(&mut self) -> &mut RouteStats {
|
||||
&mut self.stats
|
||||
}
|
||||
|
||||
pub fn has_seen_our_node_info_ts(&mut self, our_node_info_ts: Timestamp) -> bool {
|
||||
self.last_seen_our_node_info_ts == our_node_info_ts
|
||||
}
|
||||
pub fn set_last_seen_our_node_info_ts(&mut self, last_seen_our_node_info_ts: Timestamp) {
|
||||
self.last_seen_our_node_info_ts = last_seen_our_node_info_ts;
|
||||
}
|
||||
|
||||
// Check to see if this remote private route has expired
|
||||
pub fn did_expire(&self, cur_ts: Timestamp) -> bool {
|
||||
cur_ts.saturating_sub(self.last_touched_ts) >= REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY
|
||||
}
|
||||
|
||||
/// Start fresh if this had expired
|
||||
pub fn unexpire(&mut self, cur_ts: Timestamp) {
|
||||
self.last_seen_our_node_info_ts = Timestamp::new(0);
|
||||
self.last_touched_ts = cur_ts;
|
||||
self.stats = RouteStats::new(cur_ts);
|
||||
}
|
||||
|
||||
/// Note when this was last used
|
||||
pub fn touch(&mut self, cur_ts: Timestamp) {
|
||||
self.last_touched_ts = cur_ts;
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C, align(8)), derive(CheckBytes))]
|
||||
pub struct RouteSpecDetail {
|
||||
/// Crypto kind
|
||||
pub crypto_kind: CryptoKind,
|
||||
/// Secret key
|
||||
#[with(Skip)]
|
||||
pub secret_key: SecretKey,
|
||||
/// Route hops (node id keys)
|
||||
pub hops: Vec<PublicKey>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C, align(8)), derive(CheckBytes))]
|
||||
pub struct RouteSetSpecDetail {
|
||||
/// Route set per crypto kind
|
||||
route_set: BTreeMap<PublicKey, RouteSpecDetail>,
|
||||
/// Route noderefs
|
||||
#[with(Skip)]
|
||||
hop_node_refs: Vec<NodeRef>,
|
||||
/// Published private route, do not reuse for ephemeral routes
|
||||
/// Not serialized because all routes should be re-published when restarting
|
||||
#[with(Skip)]
|
||||
published: bool,
|
||||
/// Directions this route is guaranteed to work in
|
||||
#[with(RkyvEnumSet)]
|
||||
directions: DirectionSet,
|
||||
/// Stability preference (prefer reliable nodes over faster)
|
||||
stability: Stability,
|
||||
/// Sequencing capability (connection oriented protocols vs datagram)
|
||||
can_do_sequenced: bool,
|
||||
/// Stats
|
||||
stats: RouteStats,
|
||||
}
|
||||
|
||||
impl RouteSetSpecDetail {
|
||||
pub fn new(
|
||||
cur_ts: Timestamp,
|
||||
route_set: BTreeMap<PublicKey, RouteSpecDetail>,
|
||||
hop_node_refs: Vec<NodeRef>,
|
||||
directions: DirectionSet,
|
||||
stability: Stability,
|
||||
can_do_sequenced: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
route_set,
|
||||
hop_node_refs,
|
||||
published: false,
|
||||
directions,
|
||||
stability,
|
||||
can_do_sequenced,
|
||||
stats: RouteStats::new(cur_ts),
|
||||
}
|
||||
}
|
||||
pub fn get_route_by_key(&self, key: &PublicKey) -> Option<&RouteSpecDetail> {
|
||||
self.route_set.get(key)
|
||||
}
|
||||
pub fn get_route_by_key_mut(&mut self, key: &PublicKey) -> Option<&mut RouteSpecDetail> {
|
||||
self.route_set.get_mut(key)
|
||||
}
|
||||
pub fn get_route_set_keys(&self) -> TypedKeySet {
|
||||
let mut tks = TypedKeySet::new();
|
||||
for (k, v) in &self.route_set {
|
||||
tks.add(TypedKey::new(v.crypto_kind, *k));
|
||||
}
|
||||
tks
|
||||
}
|
||||
pub fn get_best_route_set_key(&self) -> Option<PublicKey> {
|
||||
self.get_route_set_keys().best().map(|k| k.value)
|
||||
}
|
||||
pub fn set_hop_node_refs(&mut self, node_refs: Vec<NodeRef>) {
|
||||
self.hop_node_refs = node_refs;
|
||||
}
|
||||
pub fn iter_route_set(
|
||||
&self,
|
||||
) -> alloc::collections::btree_map::Iter<PublicKey, RouteSpecDetail> {
|
||||
self.route_set.iter()
|
||||
}
|
||||
pub fn iter_route_set_mut(
|
||||
&mut self,
|
||||
) -> alloc::collections::btree_map::IterMut<PublicKey, RouteSpecDetail> {
|
||||
self.route_set.iter_mut()
|
||||
}
|
||||
pub fn get_stats(&self) -> &RouteStats {
|
||||
&self.stats
|
||||
}
|
||||
pub fn get_stats_mut(&mut self) -> &mut RouteStats {
|
||||
&mut self.stats
|
||||
}
|
||||
pub fn is_published(&self) -> bool {
|
||||
self.published
|
||||
}
|
||||
pub fn set_published(&mut self, published: bool) {
|
||||
self.published = published;
|
||||
}
|
||||
pub fn hop_count(&self) -> usize {
|
||||
self.hop_node_refs.len()
|
||||
}
|
||||
pub fn hop_node_ref(&self, idx: usize) -> Option<NodeRef> {
|
||||
self.hop_node_refs.get(idx).cloned()
|
||||
}
|
||||
pub fn get_stability(&self) -> Stability {
|
||||
self.stability
|
||||
}
|
||||
pub fn get_directions(&self) -> DirectionSet {
|
||||
self.directions
|
||||
}
|
||||
pub fn is_sequencing_match(&self, sequencing: Sequencing) -> bool {
|
||||
match sequencing {
|
||||
Sequencing::NoPreference => true,
|
||||
Sequencing::PreferOrdered => true,
|
||||
Sequencing::EnsureOrdered => self.can_do_sequenced,
|
||||
}
|
||||
}
|
||||
pub fn contains_nodes(&self, nodes: &[TypedKey]) -> bool {
|
||||
for tk in nodes {
|
||||
for (_pk, rsd) in &self.route_set {
|
||||
if rsd.crypto_kind == tk.kind && rsd.hops.contains(&tk.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Generate a key for the cache that can be used to uniquely identify this route's contents
|
||||
pub fn make_cache_key(&self, rti: &RoutingTableInner) -> Vec<u8> {
|
||||
let hops = &self.hop_node_refs;
|
||||
let mut cache: Vec<u8> = Vec::with_capacity(hops.len() * PUBLIC_KEY_LENGTH);
|
||||
for hop in hops {
|
||||
cache.extend_from_slice(&hop.locked(rti).best_node_id().value.bytes);
|
||||
}
|
||||
cache
|
||||
}
|
||||
}
|
1607
veilid-core/src/routing_table/route_spec_store/route_spec_store.rs
Normal file
1607
veilid-core/src/routing_table/route_spec_store/route_spec_store.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,365 @@
|
||||
use super::*;
|
||||
|
||||
// Compiled route key for caching
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct CompiledRouteCacheKey {
|
||||
sr_pubkey: PublicKey,
|
||||
pr_pubkey: PublicKey,
|
||||
}
|
||||
|
||||
/// Compiled route (safety route + private route)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CompiledRoute {
|
||||
/// The safety route attached to the private route
|
||||
pub safety_route: SafetyRoute,
|
||||
/// The secret used to encrypt the message payload
|
||||
pub secret: SecretKey,
|
||||
/// The node ref to the first hop in the compiled route
|
||||
pub first_hop: NodeRef,
|
||||
}
|
||||
|
||||
/// Ephemeral data used to help the RouteSpecStore operate efficiently
|
||||
#[derive(Debug)]
|
||||
pub struct RouteSpecStoreCache {
|
||||
/// How many times nodes have been used
|
||||
used_nodes: HashMap<PublicKey, usize>,
|
||||
/// How many times nodes have been used at the terminal point of a route
|
||||
used_end_nodes: HashMap<PublicKey, usize>,
|
||||
/// Route spec hop cache, used to quickly disqualify routes
|
||||
hop_cache: HashSet<Vec<u8>>,
|
||||
/// Remote private routes we've imported and statistics
|
||||
remote_private_route_set_cache: LruCache<RouteId, RemotePrivateRouteInfo>,
|
||||
/// Remote private routes indexed by public key
|
||||
remote_private_routes_by_key: HashMap<PublicKey, RouteId>,
|
||||
/// Compiled route cache
|
||||
compiled_route_cache: LruCache<CompiledRouteCacheKey, SafetyRoute>,
|
||||
/// List of dead allocated routes
|
||||
dead_routes: Vec<RouteId>,
|
||||
/// List of dead remote routes
|
||||
dead_remote_routes: Vec<RouteId>,
|
||||
}
|
||||
|
||||
impl RouteSpecStoreCache {
|
||||
/// add an allocated route set to our cache via its cache key
|
||||
pub fn add_to_cache(&mut self, rti: &RoutingTableInner, rssd: &RouteSetSpecDetail) {
|
||||
let cache_key = rssd.make_cache_key(rti);
|
||||
if !self.hop_cache.insert(cache_key) {
|
||||
panic!("route should never be inserted twice");
|
||||
}
|
||||
for (_pk, rsd) in rssd.iter_route_set() {
|
||||
for h in &rsd.hops {
|
||||
self.used_nodes
|
||||
.entry(*h)
|
||||
.and_modify(|e| *e += 1)
|
||||
.or_insert(1);
|
||||
}
|
||||
self.used_end_nodes
|
||||
.entry(*rsd.hops.last().unwrap())
|
||||
.and_modify(|e| *e += 1)
|
||||
.or_insert(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if an allocated route is in our cache
|
||||
pub fn contains_route(&self, cache_key: &Vec<u8>) -> bool {
|
||||
self.hop_cache.contains(cache_key)
|
||||
}
|
||||
|
||||
/// removes an allocated route set from our cache
|
||||
pub fn remove_from_cache(
|
||||
&mut self,
|
||||
rti: &RoutingTableInner,
|
||||
id: RouteId,
|
||||
rssd: &RouteSetSpecDetail,
|
||||
) -> bool {
|
||||
let cache_key = rssd.make_cache_key(rti);
|
||||
|
||||
// Remove from hop cache
|
||||
if !self.hop_cache.remove(&cache_key) {
|
||||
return false;
|
||||
}
|
||||
for (pk, rsd) in rssd.iter_route_set() {
|
||||
for h in &rsd.hops {
|
||||
// Remove from used nodes cache
|
||||
match self.used_nodes.entry(*h) {
|
||||
std::collections::hash_map::Entry::Occupied(mut o) => {
|
||||
*o.get_mut() -= 1;
|
||||
if *o.get() == 0 {
|
||||
o.remove();
|
||||
}
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(_) => {
|
||||
panic!("used_nodes cache should have contained hop");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove from end nodes cache
|
||||
match self.used_end_nodes.entry(*rsd.hops.last().unwrap()) {
|
||||
std::collections::hash_map::Entry::Occupied(mut o) => {
|
||||
*o.get_mut() -= 1;
|
||||
if *o.get() == 0 {
|
||||
o.remove();
|
||||
}
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(_) => {
|
||||
panic!("used_end_nodes cache should have contained hop");
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate compiled route cache
|
||||
self.invalidate_compiled_route_cache(pk);
|
||||
}
|
||||
|
||||
// Mark it as dead for the update
|
||||
self.dead_routes.push(id);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// calculate how many times a node with a particular node id set has been used anywhere in the path of our allocated routes
|
||||
pub fn get_used_node_count(&self, node_ids: &TypedKeySet) -> usize {
|
||||
node_ids.iter().fold(0usize, |acc, k| {
|
||||
acc + self.used_nodes.get(&k.value).cloned().unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// calculate how many times a node with a particular node id set has been used at the end of the path of our allocated routes
|
||||
pub fn get_used_end_node_count(&self, node_ids: &TypedKeySet) -> usize {
|
||||
node_ids.iter().fold(0usize, |acc, k| {
|
||||
acc + self
|
||||
.used_end_nodes
|
||||
.get(&k.value)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// add remote private route to caches
|
||||
/// returns a remote private route set id
|
||||
fn add_remote_private_route(
|
||||
&mut self,
|
||||
id: RouteId,
|
||||
rprinfo: RemotePrivateRouteInfo,
|
||||
) -> RouteId {
|
||||
// also store in id by key table
|
||||
for private_route in rprinfo.get_private_routes() {
|
||||
self.remote_private_routes_by_key
|
||||
.insert(private_route.public_key.value, id.clone());
|
||||
}
|
||||
|
||||
let mut dead = None;
|
||||
self.remote_private_route_set_cache
|
||||
.insert(id, rprinfo, |dead_id, dead_rpri| {
|
||||
dead = Some((dead_id, dead_rpri));
|
||||
});
|
||||
|
||||
if let Some((dead_id, dead_rpri)) = dead {
|
||||
// If anything LRUs out, remove from the by-key table
|
||||
// Follow the same logic as 'remove_remote_private_route' here
|
||||
for dead_private_route in dead_rpri.get_private_routes() {
|
||||
self.remote_private_routes_by_key
|
||||
.remove(&dead_private_route.public_key.value)
|
||||
.unwrap();
|
||||
self.invalidate_compiled_route_cache(&dead_private_route.public_key.value);
|
||||
}
|
||||
self.dead_remote_routes.push(dead_id);
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// get count of remote private routes in cache
|
||||
pub fn get_remote_private_route_count(&self) -> usize {
|
||||
self.remote_private_route_set_cache.len()
|
||||
}
|
||||
|
||||
/// iterate all of the remote private routes we have in the cache
|
||||
pub fn iter_remote_private_routes(
|
||||
&self,
|
||||
) -> hashlink::linked_hash_map::Iter<RouteId, RemotePrivateRouteInfo> {
|
||||
self.remote_private_route_set_cache.iter()
|
||||
}
|
||||
|
||||
/// remote private route cache accessor
|
||||
/// will LRU entries and may expire entries and not return them if they are stale
|
||||
pub fn get_remote_private_route(
|
||||
&mut self,
|
||||
cur_ts: Timestamp,
|
||||
id: &RouteId,
|
||||
) -> Option<&RemotePrivateRouteInfo> {
|
||||
if let Some(rpri) = self.remote_private_route_set_cache.get_mut(id) {
|
||||
if !rpri.did_expire(cur_ts) {
|
||||
rpri.touch(cur_ts);
|
||||
return Some(rpri);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// mutable remote private route cache accessor
|
||||
/// will LRU entries and may expire entries and not return them if they are stale
|
||||
pub fn get_remote_private_route_mut(
|
||||
&mut self,
|
||||
cur_ts: Timestamp,
|
||||
id: &RouteId,
|
||||
) -> Option<&mut RemotePrivateRouteInfo> {
|
||||
if let Some(rpri) = self.remote_private_route_set_cache.get_mut(id) {
|
||||
if !rpri.did_expire(cur_ts) {
|
||||
rpri.touch(cur_ts);
|
||||
return Some(rpri);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// mutable remote private route cache accessor without lru action
|
||||
/// will not LRU entries but may expire entries and not return them if they are stale
|
||||
pub fn peek_remote_private_route_mut(
|
||||
&mut self,
|
||||
cur_ts: Timestamp,
|
||||
id: &RouteId,
|
||||
) -> Option<&mut RemotePrivateRouteInfo> {
|
||||
if let Some(rpri) = self.remote_private_route_set_cache.peek_mut(id) {
|
||||
if !rpri.did_expire(cur_ts) {
|
||||
rpri.touch(cur_ts);
|
||||
return Some(rpri);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// look up a remote private route id by one of the route public keys
|
||||
pub fn get_remote_private_route_id_by_key(&self, key: &PublicKey) -> Option<RouteId> {
|
||||
self.remote_private_routes_by_key.get(key).cloned()
|
||||
}
|
||||
|
||||
/// get or create a remote private route cache entry
|
||||
/// may LRU and/or expire other cache entries to make room for the new one
|
||||
/// or update an existing entry with the same private route set
|
||||
/// returns the route set id
|
||||
pub fn cache_remote_private_route(
|
||||
&mut self,
|
||||
cur_ts: Timestamp,
|
||||
id: RouteId,
|
||||
private_routes: Vec<PrivateRoute>,
|
||||
) {
|
||||
// get id for this route set
|
||||
if let Some(rpri) = self.get_remote_private_route_mut(cur_ts, &id) {
|
||||
if rpri.did_expire(cur_ts) {
|
||||
// Start fresh if this had expired
|
||||
rpri.unexpire(cur_ts);
|
||||
} else {
|
||||
// If not expired, just mark as being used
|
||||
rpri.touch(cur_ts);
|
||||
}
|
||||
} else {
|
||||
// New remote private route cache entry
|
||||
let rpri = RemotePrivateRouteInfo::new(private_routes, cur_ts);
|
||||
|
||||
self.add_remote_private_route(id, rpri);
|
||||
if self.peek_remote_private_route_mut(cur_ts, &id).is_none() {
|
||||
panic!("remote private route should exist");
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/// remove a remote private route from the cache
|
||||
pub fn remove_remote_private_route(&mut self, id: RouteId) -> bool {
|
||||
let Some(rprinfo) = self.remote_private_route_set_cache.remove(&id) else {
|
||||
return false;
|
||||
};
|
||||
for private_route in rprinfo.get_private_routes() {
|
||||
self.remote_private_routes_by_key
|
||||
.remove(&private_route.public_key.value)
|
||||
.unwrap();
|
||||
self.invalidate_compiled_route_cache(&private_route.public_key.value);
|
||||
}
|
||||
self.dead_remote_routes.push(id);
|
||||
true
|
||||
}
|
||||
|
||||
/// Stores a compiled 'safety + private' route so we don't have to compile it again later
|
||||
pub fn add_to_compiled_route_cache(&mut self, pr_pubkey: PublicKey, safety_route: SafetyRoute) {
|
||||
let key = CompiledRouteCacheKey {
|
||||
sr_pubkey: safety_route.public_key.value,
|
||||
pr_pubkey,
|
||||
};
|
||||
|
||||
if let Some(v) = self
|
||||
.compiled_route_cache
|
||||
.insert(key, safety_route, |_k, _v| {
|
||||
// Do nothing on LRU evict
|
||||
})
|
||||
{
|
||||
log_rtab!(error "route cache already contained key: sr_pubkey={:?}, pr_pubkey={:?}", v.public_key, pr_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks up an existing compiled route from the safety and private route components
|
||||
pub fn lookup_compiled_route_cache(
|
||||
&mut self,
|
||||
sr_pubkey: PublicKey,
|
||||
pr_pubkey: PublicKey,
|
||||
) -> Option<SafetyRoute> {
|
||||
let key = CompiledRouteCacheKey {
|
||||
sr_pubkey,
|
||||
pr_pubkey,
|
||||
};
|
||||
self.compiled_route_cache.get(&key).cloned()
|
||||
}
|
||||
|
||||
/// When routes are dropped, they should be removed from the compiled route cache
|
||||
fn invalidate_compiled_route_cache(&mut self, dead_key: &PublicKey) {
|
||||
let mut dead_entries = Vec::new();
|
||||
for (k, _v) in self.compiled_route_cache.iter() {
|
||||
if k.sr_pubkey == *dead_key || k.pr_pubkey == *dead_key {
|
||||
dead_entries.push(k.clone());
|
||||
}
|
||||
}
|
||||
for d in dead_entries {
|
||||
self.compiled_route_cache.remove(&d);
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the dead local and remote routes so we can update clients
|
||||
pub fn take_dead_routes(&mut self) -> Option<(Vec<RouteId>, Vec<RouteId>)> {
|
||||
if self.dead_routes.is_empty() && self.dead_remote_routes.is_empty() {
|
||||
// Nothing to do
|
||||
return None;
|
||||
}
|
||||
let dead_routes = core::mem::take(&mut self.dead_routes);
|
||||
let dead_remote_routes = core::mem::take(&mut self.dead_remote_routes);
|
||||
Some((dead_routes, dead_remote_routes))
|
||||
}
|
||||
|
||||
/// Clean up imported remote routes
|
||||
/// Resets statistics for when our node info changes
|
||||
pub fn reset_remote_private_routes(&mut self) {
|
||||
// Restart stats for routes so we test the route again
|
||||
for (_k, v) in self.remote_private_route_set_cache.iter_mut() {
|
||||
v.get_stats_mut().reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// Roll transfer statistics
|
||||
pub fn roll_transfers(&mut self, last_ts: Timestamp, cur_ts: Timestamp) {
|
||||
for (_k, v) in self.remote_private_route_set_cache.iter_mut() {
|
||||
v.get_stats_mut().roll_transfers(last_ts, cur_ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RouteSpecStoreCache {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
used_nodes: Default::default(),
|
||||
used_end_nodes: Default::default(),
|
||||
hop_cache: Default::default(),
|
||||
remote_private_route_set_cache: LruCache::new(REMOTE_PRIVATE_ROUTE_CACHE_SIZE),
|
||||
remote_private_routes_by_key: HashMap::new(),
|
||||
compiled_route_cache: LruCache::new(COMPILED_ROUTE_CACHE_SIZE),
|
||||
dead_routes: Default::default(),
|
||||
dead_remote_routes: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
use super::*;
|
||||
|
||||
/// The core representation of the RouteSpecStore that can be serialized
|
||||
#[derive(Debug, Clone, Default, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C, align(8)), derive(CheckBytes))]
|
||||
pub struct RouteSpecStoreContent {
|
||||
/// All of the route sets we have allocated so far indexed by key
|
||||
id_by_key: HashMap<PublicKey, RouteId>,
|
||||
/// All of the route sets we have allocated so far
|
||||
details: HashMap<RouteId, RouteSetSpecDetail>,
|
||||
}
|
||||
|
||||
impl RouteSpecStoreContent {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id_by_key: HashMap::new(),
|
||||
details: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load(routing_table: RoutingTable) -> EyreResult<RouteSpecStoreContent> {
|
||||
// Deserialize what we can
|
||||
let table_store = routing_table.network_manager().table_store();
|
||||
let rsstdb = table_store.open("RouteSpecStore", 1).await?;
|
||||
let mut content: RouteSpecStoreContent =
|
||||
rsstdb.load_rkyv(0, b"content")?.unwrap_or_default();
|
||||
|
||||
// Look up all route hop noderefs since we can't serialize those
|
||||
let mut dead_ids = Vec::new();
|
||||
for (rsid, rssd) in content.details.iter_mut() {
|
||||
// Get best route since they all should resolve
|
||||
let Some(pk) = rssd.get_best_route_set_key() else {
|
||||
dead_ids.push(rsid.clone());
|
||||
continue;
|
||||
};
|
||||
let Some(rsd) = rssd.get_route_by_key(&pk) else {
|
||||
dead_ids.push(rsid.clone());
|
||||
continue;
|
||||
};
|
||||
// Go through best route and resolve noderefs
|
||||
let mut hop_node_refs = Vec::with_capacity(rsd.hops.len());
|
||||
for h in &rsd.hops {
|
||||
let Some(nr) = routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) else {
|
||||
dead_ids.push(rsid.clone());
|
||||
break;
|
||||
};
|
||||
hop_node_refs.push(nr);
|
||||
}
|
||||
|
||||
// Apply noderefs
|
||||
rssd.set_hop_node_refs(hop_node_refs);
|
||||
}
|
||||
for id in dead_ids {
|
||||
log_rtab!(debug "no entry, killing off private route: {}", id);
|
||||
content.remove_detail(&id);
|
||||
}
|
||||
|
||||
// Load secrets from pstore
|
||||
let pstore = routing_table.network_manager().protected_store();
|
||||
let secret_key_map: HashMap<PublicKey, SecretKey> = pstore
|
||||
.load_user_secret_rkyv("RouteSpecStore")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Ensure we got secret keys for all the public keys
|
||||
let mut got_secret_key_ids = HashSet::new();
|
||||
for (rsid, rssd) in content.details.iter_mut() {
|
||||
let mut found_all = true;
|
||||
for (pk, rsd) in rssd.iter_route_set_mut() {
|
||||
if let Some(sk) = secret_key_map.get(pk) {
|
||||
rsd.secret_key = *sk;
|
||||
} else {
|
||||
found_all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found_all {
|
||||
got_secret_key_ids.insert(rsid.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// If we missed any, nuke those route ids
|
||||
let dead_ids: Vec<RouteId> = content
|
||||
.details
|
||||
.keys()
|
||||
.filter_map(|id| {
|
||||
if !got_secret_key_ids.contains(id) {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for id in dead_ids {
|
||||
log_rtab!(debug "missing secret key, killing off private route: {}", id);
|
||||
content.remove_detail(&id);
|
||||
}
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub async fn save(&self, routing_table: RoutingTable) -> EyreResult<()> {
|
||||
// Save all the fields we care about to the frozen blob in table storage
|
||||
// This skips #[with(Skip)] saving the secret keys, we save them in the protected store instead
|
||||
let table_store = routing_table.network_manager().table_store();
|
||||
let rsstdb = table_store.open("RouteSpecStore", 1).await?;
|
||||
rsstdb.store_rkyv(0, b"content", self).await?;
|
||||
|
||||
// Keep secrets in protected store as well
|
||||
let pstore = routing_table.network_manager().protected_store();
|
||||
|
||||
let mut out: HashMap<PublicKey, SecretKey> = HashMap::new();
|
||||
for (_rsid, rssd) in self.details.iter() {
|
||||
for (pk, rsd) in rssd.iter_route_set() {
|
||||
out.insert(*pk, rsd.secret_key);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = pstore.save_user_secret_rkyv("RouteSpecStore", &out).await?; // ignore if this previously existed or not
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_detail(&mut self, id: RouteId, detail: RouteSetSpecDetail) {
|
||||
assert!(!self.details.contains_key(&id));
|
||||
|
||||
// also store in id by key table
|
||||
for (pk, _) in detail.iter_route_set() {
|
||||
self.id_by_key.insert(*pk, id.clone());
|
||||
}
|
||||
self.details.insert(id.clone(), detail);
|
||||
}
|
||||
pub fn remove_detail(&mut self, id: &RouteId) -> Option<RouteSetSpecDetail> {
|
||||
let detail = self.details.remove(id)?;
|
||||
for (pk, _) in detail.iter_route_set() {
|
||||
self.id_by_key.remove(&pk).unwrap();
|
||||
}
|
||||
Some(detail)
|
||||
}
|
||||
pub fn get_detail_count(&self) -> usize {
|
||||
self.details.len()
|
||||
}
|
||||
pub fn get_detail(&self, id: &RouteId) -> Option<&RouteSetSpecDetail> {
|
||||
self.details.get(id)
|
||||
}
|
||||
pub fn get_detail_mut(&mut self, id: &RouteId) -> Option<&mut RouteSetSpecDetail> {
|
||||
self.details.get_mut(id)
|
||||
}
|
||||
pub fn get_id_by_key(&self, key: &PublicKey) -> Option<RouteId> {
|
||||
self.id_by_key.get(key).cloned()
|
||||
}
|
||||
pub fn iter_ids(&self) -> std::collections::hash_map::Keys<RouteId, RouteSetSpecDetail> {
|
||||
self.details.keys()
|
||||
}
|
||||
pub fn iter_details(&self) -> std::collections::hash_map::Iter<RouteId, RouteSetSpecDetail> {
|
||||
self.details.iter()
|
||||
}
|
||||
|
||||
/// Clean up local allocated routes
|
||||
/// Resets publication status and statistics for when our node info changes
|
||||
/// Routes must be republished
|
||||
pub fn reset_details(&mut self) {
|
||||
for (_k, v) in &mut self.details {
|
||||
// Must republish route now
|
||||
v.set_published(false);
|
||||
// Restart stats for routes so we test the route again
|
||||
v.get_stats_mut().reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// Roll transfer statistics
|
||||
pub fn roll_transfers(&mut self, last_ts: Timestamp, cur_ts: Timestamp) {
|
||||
for rssd in self.details.values_mut() {
|
||||
rssd.get_stats_mut().roll_transfers(last_ts, cur_ts);
|
||||
}
|
||||
}
|
||||
}
|
129
veilid-core/src/routing_table/route_spec_store/route_stats.rs
Normal file
129
veilid-core/src/routing_table/route_spec_store/route_stats.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Default, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct RouteStats {
|
||||
/// Consecutive failed to send count
|
||||
#[with(Skip)]
|
||||
pub failed_to_send: u32,
|
||||
/// Questions lost
|
||||
#[with(Skip)]
|
||||
pub questions_lost: u32,
|
||||
/// Timestamp of when the route was created
|
||||
pub created_ts: Timestamp,
|
||||
/// Timestamp of when the route was last checked for validity
|
||||
#[with(Skip)]
|
||||
pub last_tested_ts: Option<Timestamp>,
|
||||
/// Timestamp of when the route was last sent to
|
||||
#[with(Skip)]
|
||||
pub last_sent_ts: Option<Timestamp>,
|
||||
/// Timestamp of when the route was last received over
|
||||
#[with(Skip)]
|
||||
pub last_received_ts: Option<Timestamp>,
|
||||
/// Transfers up and down
|
||||
pub transfer_stats_down_up: TransferStatsDownUp,
|
||||
/// Latency stats
|
||||
pub latency_stats: LatencyStats,
|
||||
/// Accounting mechanism for this route's RPC latency
|
||||
#[with(Skip)]
|
||||
latency_stats_accounting: LatencyStatsAccounting,
|
||||
/// Accounting mechanism for the bandwidth across this route
|
||||
#[with(Skip)]
|
||||
transfer_stats_accounting: TransferStatsAccounting,
|
||||
}
|
||||
|
||||
impl RouteStats {
|
||||
/// Make new route stats
|
||||
pub fn new(created_ts: Timestamp) -> Self {
|
||||
Self {
|
||||
created_ts,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
/// Mark a route as having failed to send
|
||||
pub fn record_send_failed(&mut self) {
|
||||
self.failed_to_send += 1;
|
||||
}
|
||||
|
||||
/// Mark a route as having lost a question
|
||||
pub fn record_question_lost(&mut self) {
|
||||
self.questions_lost += 1;
|
||||
}
|
||||
|
||||
/// Mark a route as having received something
|
||||
pub fn record_received(&mut self, cur_ts: Timestamp, bytes: ByteCount) {
|
||||
self.last_received_ts = Some(cur_ts);
|
||||
self.last_tested_ts = Some(cur_ts);
|
||||
self.transfer_stats_accounting.add_down(bytes);
|
||||
}
|
||||
|
||||
/// Mark a route as having been sent to
|
||||
pub fn record_sent(&mut self, cur_ts: Timestamp, bytes: ByteCount) {
|
||||
self.last_sent_ts = Some(cur_ts);
|
||||
self.transfer_stats_accounting.add_up(bytes);
|
||||
}
|
||||
|
||||
/// Mark a route as having been sent to
|
||||
pub fn record_latency(&mut self, latency: TimestampDuration) {
|
||||
self.latency_stats = self.latency_stats_accounting.record_latency(latency);
|
||||
}
|
||||
|
||||
/// Mark a route as having been tested
|
||||
pub fn record_tested(&mut self, cur_ts: Timestamp) {
|
||||
self.last_tested_ts = Some(cur_ts);
|
||||
|
||||
// Reset question_lost and failed_to_send if we test clean
|
||||
self.failed_to_send = 0;
|
||||
self.questions_lost = 0;
|
||||
}
|
||||
|
||||
/// Roll transfers for these route stats
|
||||
pub fn roll_transfers(&mut self, last_ts: Timestamp, cur_ts: Timestamp) {
|
||||
self.transfer_stats_accounting.roll_transfers(
|
||||
last_ts,
|
||||
cur_ts,
|
||||
&mut self.transfer_stats_down_up,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the latency stats
|
||||
pub fn latency_stats(&self) -> &LatencyStats {
|
||||
&self.latency_stats
|
||||
}
|
||||
|
||||
/// Get the transfer stats
|
||||
pub fn transfer_stats(&self) -> &TransferStatsDownUp {
|
||||
&self.transfer_stats_down_up
|
||||
}
|
||||
|
||||
/// Reset stats when network restarts
|
||||
pub fn reset(&mut self) {
|
||||
self.last_tested_ts = None;
|
||||
self.last_sent_ts = None;
|
||||
self.last_received_ts = None;
|
||||
}
|
||||
|
||||
/// Check if a route needs testing
|
||||
pub fn needs_testing(&self, cur_ts: Timestamp) -> bool {
|
||||
// Has the route had any failures lately?
|
||||
if self.questions_lost > 0 || self.failed_to_send > 0 {
|
||||
// If so, always test
|
||||
return true;
|
||||
}
|
||||
|
||||
// Has the route been tested within the idle time we'd want to check things?
|
||||
// (also if we've received successfully over the route, this will get set)
|
||||
if let Some(last_tested_ts) = self.last_tested_ts {
|
||||
if cur_ts.saturating_sub(last_tested_ts)
|
||||
> TimestampDuration::new(ROUTE_MIN_IDLE_TIME_MS as u64 * 1000u64)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// If this route has never been tested, it needs to be
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
@ -102,7 +102,7 @@ impl RoutingDomainEditor {
|
||||
|
||||
let mut changed = false;
|
||||
{
|
||||
let node_id = self.routing_table.node_id();
|
||||
let node_ids = self.routing_table.node_ids();
|
||||
|
||||
let mut inner = self.routing_table.inner.write();
|
||||
inner.with_routing_domain_mut(self.routing_domain, |detail| {
|
||||
@ -134,9 +134,7 @@ impl RoutingDomainEditor {
|
||||
|
||||
info!(
|
||||
"{:?} Dial Info: {}@{}",
|
||||
self.routing_domain,
|
||||
NodeId::new(node_id),
|
||||
dial_info_detail.dial_info
|
||||
self.routing_domain, node_ids, dial_info_detail.dial_info
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ pub enum ContactMethod {
|
||||
/// Contact the node directly
|
||||
Direct(DialInfo),
|
||||
/// Request via signal the node connect back directly (relay, target)
|
||||
SignalReverse(DHTKey, DHTKey),
|
||||
/// Request via signal the node negotiate a hole punch (relay, target_node)
|
||||
SignalHolePunch(DHTKey, DHTKey),
|
||||
SignalReverse(TypedKey, TypedKey),
|
||||
/// Request via signal the node negotiate a hole punch (relay, target)
|
||||
SignalHolePunch(TypedKey, TypedKey),
|
||||
/// Must use an inbound relay to reach the node
|
||||
InboundRelay(DHTKey),
|
||||
InboundRelay(TypedKey),
|
||||
/// Must use outbound relay to reach the node
|
||||
OutboundRelay(DHTKey),
|
||||
OutboundRelay(TypedKey),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -106,8 +106,8 @@ impl RoutingDomainDetailCommon {
|
||||
network_class: self.network_class.unwrap_or(NetworkClass::Invalid),
|
||||
outbound_protocols: self.outbound_protocols,
|
||||
address_types: self.address_types,
|
||||
min_version: MIN_CRYPTO_VERSION,
|
||||
max_version: MAX_CRYPTO_VERSION,
|
||||
envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(),
|
||||
crypto_support: VALID_CRYPTO_KINDS.to_vec(),
|
||||
dial_info_detail_list: self.dial_info_details.clone(),
|
||||
};
|
||||
|
||||
@ -118,7 +118,7 @@ impl RoutingDomainDetailCommon {
|
||||
let opt_relay_pi = rn.locked(rti).make_peer_info(self.routing_domain);
|
||||
if let Some(relay_pi) = opt_relay_pi {
|
||||
match relay_pi.signed_node_info {
|
||||
SignedNodeInfo::Direct(d) => Some((relay_pi.node_id, d)),
|
||||
SignedNodeInfo::Direct(d) => Some((relay_pi.node_ids, d)),
|
||||
SignedNodeInfo::Relayed(_) => {
|
||||
warn!("relay node should not have a relay itself! if this happens, a relay updated its signed node info and became a relay, which should cause the relay to be dropped");
|
||||
None
|
||||
@ -130,27 +130,27 @@ impl RoutingDomainDetailCommon {
|
||||
});
|
||||
|
||||
let signed_node_info = match relay_info {
|
||||
Some((relay_id, relay_sdni)) => SignedNodeInfo::Relayed(
|
||||
SignedRelayedNodeInfo::with_secret(
|
||||
NodeId::new(rti.unlocked_inner.node_id),
|
||||
Some((relay_ids, relay_sdni)) => SignedNodeInfo::Relayed(
|
||||
SignedRelayedNodeInfo::make_signatures(
|
||||
rti.unlocked_inner.crypto(),
|
||||
rti.unlocked_inner.node_id_typed_key_pairs(),
|
||||
node_info,
|
||||
relay_id,
|
||||
relay_ids,
|
||||
relay_sdni,
|
||||
&rti.unlocked_inner.node_id_secret,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
None => SignedNodeInfo::Direct(
|
||||
SignedDirectNodeInfo::with_secret(
|
||||
NodeId::new(rti.unlocked_inner.node_id),
|
||||
SignedDirectNodeInfo::make_signatures(
|
||||
rti.unlocked_inner.crypto(),
|
||||
rti.unlocked_inner.node_id_typed_key_pairs(),
|
||||
node_info,
|
||||
&rti.unlocked_inner.node_id_secret,
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
};
|
||||
|
||||
PeerInfo::new(NodeId::new(rti.unlocked_inner.node_id), signed_node_info)
|
||||
PeerInfo::new(rti.unlocked_inner.node_ids(), signed_node_info)
|
||||
}
|
||||
|
||||
pub fn with_peer_info<F, R>(&self, rti: &RoutingTableInner, f: F) -> R
|
||||
@ -280,7 +280,17 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
// Get the nodeinfos for convenience
|
||||
let node_a = peer_a.signed_node_info.node_info();
|
||||
let node_b = peer_b.signed_node_info.node_info();
|
||||
|
||||
|
||||
// Get the node ids that would be used between these peers
|
||||
let cck = common_crypto_kinds(&peer_a.node_ids.kinds(), &peer_b.node_ids.kinds());
|
||||
let Some(best_ck) = cck.first().copied() else {
|
||||
// No common crypto kinds between these nodes, can't contact
|
||||
return ContactMethod::Unreachable;
|
||||
};
|
||||
|
||||
//let node_a_id = peer_a.node_ids.get(best_ck).unwrap();
|
||||
let node_b_id = peer_b.node_ids.get(best_ck).unwrap();
|
||||
|
||||
// Get the best match dial info for node B if we have it
|
||||
if let Some(target_did) =
|
||||
first_filtered_dial_info_detail(node_a, node_b, &dial_info_filter, sequencing)
|
||||
@ -293,15 +303,20 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
|
||||
// Get the target's inbound relay, it must have one or it is not reachable
|
||||
if let Some(node_b_relay) = peer_b.signed_node_info.relay_info() {
|
||||
let node_b_relay_id = peer_b.signed_node_info.relay_id().unwrap();
|
||||
|
||||
// Note that relay_peer_info could be node_a, in which case a connection already exists
|
||||
// and we only get here if the connection had dropped, in which case node_a is unreachable until
|
||||
// it gets a new relay connection up
|
||||
if node_b_relay_id.key == peer_a.node_id.key {
|
||||
if peer_b.signed_node_info.relay_ids().contains_any(&peer_a.node_ids) {
|
||||
return ContactMethod::Existing;
|
||||
}
|
||||
|
||||
// Get best node id to contact relay with
|
||||
let Some(node_b_relay_id) = peer_b.signed_node_info.relay_ids().get(best_ck) else {
|
||||
// No best relay id
|
||||
return ContactMethod::Unreachable;
|
||||
};
|
||||
|
||||
// Can node A reach the inbound relay directly?
|
||||
if first_filtered_dial_info_detail(
|
||||
node_a,
|
||||
@ -329,8 +344,8 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
// Can we receive a direct reverse connection?
|
||||
if !reverse_did.class.requires_signal() {
|
||||
return ContactMethod::SignalReverse(
|
||||
node_b_relay_id.key,
|
||||
peer_b.node_id.key,
|
||||
node_b_relay_id,
|
||||
node_b_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -361,8 +376,8 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
{
|
||||
// The target and ourselves have a udp dialinfo that they can reach
|
||||
return ContactMethod::SignalHolePunch(
|
||||
node_b_relay_id.key,
|
||||
peer_a.node_id.key,
|
||||
node_b_relay_id,
|
||||
node_b_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -370,21 +385,26 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
// Otherwise we have to inbound relay
|
||||
}
|
||||
|
||||
return ContactMethod::InboundRelay(node_b_relay_id.key);
|
||||
return ContactMethod::InboundRelay(node_b_relay_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the node B has no direct dial info, it needs to have an inbound relay
|
||||
else if let Some(node_b_relay) = peer_b.signed_node_info.relay_info() {
|
||||
let node_b_relay_id = peer_b.signed_node_info.relay_id().unwrap();
|
||||
|
||||
|
||||
// Note that relay_peer_info could be node_a, in which case a connection already exists
|
||||
// and we only get here if the connection had dropped, in which case node_a is unreachable until
|
||||
// it gets a new relay connection up
|
||||
if node_b_relay_id.key == peer_a.node_id.key {
|
||||
if peer_b.signed_node_info.relay_ids().contains_any(&peer_a.node_ids) {
|
||||
return ContactMethod::Existing;
|
||||
}
|
||||
|
||||
// Get best node id to contact relay with
|
||||
let Some(node_b_relay_id) = peer_b.signed_node_info.relay_ids().get(best_ck) else {
|
||||
// No best relay id
|
||||
return ContactMethod::Unreachable;
|
||||
};
|
||||
|
||||
// Can we reach the full relay?
|
||||
if first_filtered_dial_info_detail(
|
||||
node_a,
|
||||
@ -394,13 +414,13 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail {
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return ContactMethod::InboundRelay(node_b_relay_id.key);
|
||||
return ContactMethod::InboundRelay(node_b_relay_id);
|
||||
}
|
||||
}
|
||||
|
||||
// If node A can't reach the node by other means, it may need to use its own relay
|
||||
if let Some(node_a_relay_id) = peer_a.signed_node_info.relay_id() {
|
||||
return ContactMethod::OutboundRelay(node_a_relay_id.key);
|
||||
if let Some(node_a_relay_id) = peer_a.signed_node_info.relay_ids().get(best_ck) {
|
||||
return ContactMethod::OutboundRelay(node_a_relay_id);
|
||||
}
|
||||
|
||||
ContactMethod::Unreachable
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,23 +3,123 @@ use super::*;
|
||||
use futures_util::stream::{FuturesUnordered, StreamExt};
|
||||
use stop_token::future::FutureExt as StopFutureExt;
|
||||
|
||||
pub const BOOTSTRAP_TXT_VERSION: u8 = 0;
|
||||
pub const BOOTSTRAP_TXT_VERSION_0: u8 = 0;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BootstrapRecord {
|
||||
min_version: u8,
|
||||
max_version: u8,
|
||||
node_ids: TypedKeySet,
|
||||
envelope_support: Vec<u8>,
|
||||
dial_info_details: Vec<DialInfoDetail>,
|
||||
}
|
||||
pub type BootstrapRecordMap = BTreeMap<DHTKey, BootstrapRecord>;
|
||||
impl BootstrapRecord {
|
||||
pub fn merge(&mut self, other: BootstrapRecord) {
|
||||
self.node_ids.add_all(&other.node_ids);
|
||||
for x in other.envelope_support {
|
||||
if !self.envelope_support.contains(&x) {
|
||||
self.envelope_support.push(x);
|
||||
self.envelope_support.sort();
|
||||
}
|
||||
}
|
||||
for did in other.dial_info_details {
|
||||
if !self.dial_info_details.contains(&did) {
|
||||
self.dial_info_details.push(did);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RoutingTable {
|
||||
/// Process bootstrap version 0
|
||||
async fn process_bootstrap_records_v0(
|
||||
&self,
|
||||
records: Vec<String>,
|
||||
) -> EyreResult<Option<BootstrapRecord>> {
|
||||
// Bootstrap TXT Record Format Version 0:
|
||||
// txt_version|envelope_support|node_ids|hostname|dialinfoshort*
|
||||
//
|
||||
// Split bootstrap node record by '|' and then lists by ','. Example:
|
||||
// 0|0|VLD0:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ|bootstrap-1.dev.veilid.net|T5150,U5150,W5150/ws
|
||||
|
||||
if records.len() != 5 {
|
||||
bail!("invalid number of fields in bootstrap v0 txt record");
|
||||
}
|
||||
|
||||
// Envelope support
|
||||
let mut envelope_support = Vec::new();
|
||||
for ess in records[1].split(",") {
|
||||
let ess = ess.trim();
|
||||
let es = match ess.parse::<u8>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"invalid envelope version specified in bootstrap node txt record: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
envelope_support.push(es);
|
||||
}
|
||||
envelope_support.dedup();
|
||||
envelope_support.sort();
|
||||
|
||||
// Node Id
|
||||
let mut node_ids = TypedKeySet::new();
|
||||
for node_id_str in records[2].split(",") {
|
||||
let node_id_str = node_id_str.trim();
|
||||
let node_id = match TypedKey::from_str(&node_id_str) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Invalid node id in bootstrap node record {}: {}",
|
||||
node_id_str,
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
node_ids.add(node_id);
|
||||
}
|
||||
|
||||
// If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node
|
||||
if self.unlocked_inner.matches_own_node_id(&node_ids) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Hostname
|
||||
let hostname_str = records[3].trim();
|
||||
|
||||
// Resolve each record and store in node dial infos list
|
||||
let mut dial_info_details = Vec::new();
|
||||
for rec in records[4].split(",") {
|
||||
let rec = rec.trim();
|
||||
let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) {
|
||||
Ok(dis) => dis,
|
||||
Err(e) => {
|
||||
warn!("Couldn't resolve bootstrap node dial info {}: {}", rec, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for di in dial_infos {
|
||||
dial_info_details.push(DialInfoDetail {
|
||||
dial_info: di,
|
||||
class: DialInfoClass::Direct,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(BootstrapRecord {
|
||||
node_ids,
|
||||
envelope_support,
|
||||
dial_info_details,
|
||||
}))
|
||||
}
|
||||
|
||||
// Bootstrap lookup process
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
pub(crate) async fn resolve_bootstrap(
|
||||
&self,
|
||||
bootstrap: Vec<String>,
|
||||
) -> EyreResult<BootstrapRecordMap> {
|
||||
) -> EyreResult<Vec<BootstrapRecord>> {
|
||||
// Resolve from bootstrap root to bootstrap hostnames
|
||||
let mut bsnames = Vec::<String>::new();
|
||||
for bh in bootstrap {
|
||||
@ -58,22 +158,14 @@ impl RoutingTable {
|
||||
Ok(v) => v,
|
||||
};
|
||||
// for each record resolve into key/bootstraprecord pairs
|
||||
let mut bootstrap_records: Vec<(DHTKey, BootstrapRecord)> = Vec::new();
|
||||
let mut bootstrap_records: Vec<BootstrapRecord> = Vec::new();
|
||||
for bsnirecord in bsnirecords {
|
||||
// Bootstrap TXT Record Format Version 0:
|
||||
// txt_version,min_version,max_version,nodeid,hostname,dialinfoshort*
|
||||
//
|
||||
// Split bootstrap node record by commas. Example:
|
||||
// 0,0,0,7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ,bootstrap-1.dev.veilid.net,T5150,U5150,W5150/ws
|
||||
// All formats split on '|' character
|
||||
let records: Vec<String> = bsnirecord
|
||||
.trim()
|
||||
.split(',')
|
||||
.split('|')
|
||||
.map(|x| x.trim().to_owned())
|
||||
.collect();
|
||||
if records.len() < 6 {
|
||||
warn!("invalid number of fields in bootstrap txt record");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bootstrap TXT record version
|
||||
let txt_version: u8 = match records[0].parse::<u8>() {
|
||||
@ -86,81 +178,30 @@ impl RoutingTable {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if txt_version != BOOTSTRAP_TXT_VERSION {
|
||||
warn!("unsupported bootstrap txt record version");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Min/Max wire protocol version
|
||||
let min_version: u8 = match records[1].parse::<u8>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"invalid min_version specified in bootstrap node txt record: {}",
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let max_version: u8 = match records[2].parse::<u8>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"invalid max_version specified in bootstrap node txt record: {}",
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Node Id
|
||||
let node_id_str = &records[3];
|
||||
let node_id_key = match DHTKey::try_decode(node_id_str) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Invalid node id in bootstrap node record {}: {}",
|
||||
node_id_str, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Hostname
|
||||
let hostname_str = &records[4];
|
||||
|
||||
// If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node
|
||||
if self.node_id() == node_id_key {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve each record and store in node dial infos list
|
||||
let mut bootstrap_record = BootstrapRecord {
|
||||
min_version,
|
||||
max_version,
|
||||
dial_info_details: Vec::new(),
|
||||
};
|
||||
for rec in &records[5..] {
|
||||
let rec = rec.trim();
|
||||
let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) {
|
||||
Ok(dis) => dis,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Couldn't resolve bootstrap node dial info {}: {}",
|
||||
rec, e
|
||||
);
|
||||
continue;
|
||||
let bootstrap_record = match txt_version {
|
||||
BOOTSTRAP_TXT_VERSION_0 => {
|
||||
match self.process_bootstrap_records_v0(records).await {
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"couldn't process v0 bootstrap records from {}: {}",
|
||||
bsname, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => {
|
||||
// skipping
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for di in dial_infos {
|
||||
bootstrap_record.dial_info_details.push(DialInfoDetail {
|
||||
dial_info: di,
|
||||
class: DialInfoClass::Direct,
|
||||
});
|
||||
}
|
||||
}
|
||||
bootstrap_records.push((node_id_key, bootstrap_record));
|
||||
_ => {
|
||||
warn!("unsupported bootstrap txt record version");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
bootstrap_records.push(bootstrap_record);
|
||||
}
|
||||
Some(bootstrap_records)
|
||||
}
|
||||
@ -168,21 +209,35 @@ impl RoutingTable {
|
||||
);
|
||||
}
|
||||
|
||||
let mut bsmap = BootstrapRecordMap::new();
|
||||
let mut merged_bootstrap_records: Vec<BootstrapRecord> = Vec::new();
|
||||
while let Some(bootstrap_records) = unord.next().await {
|
||||
if let Some(bootstrap_records) = bootstrap_records {
|
||||
for (bskey, mut bsrec) in bootstrap_records {
|
||||
let rec = bsmap.entry(bskey).or_insert_with(|| BootstrapRecord {
|
||||
min_version: bsrec.min_version,
|
||||
max_version: bsrec.max_version,
|
||||
dial_info_details: Vec::new(),
|
||||
});
|
||||
rec.dial_info_details.append(&mut bsrec.dial_info_details);
|
||||
let Some(bootstrap_records) = bootstrap_records else {
|
||||
continue;
|
||||
};
|
||||
for mut bsrec in bootstrap_records {
|
||||
let mut mbi = 0;
|
||||
while mbi < merged_bootstrap_records.len() {
|
||||
let mbr = &mut merged_bootstrap_records[mbi];
|
||||
if mbr.node_ids.contains_any(&bsrec.node_ids) {
|
||||
// Merge record, pop this one out
|
||||
let mbr = merged_bootstrap_records.remove(mbi);
|
||||
bsrec.merge(mbr);
|
||||
} else {
|
||||
// No overlap, go to next record
|
||||
mbi += 1;
|
||||
}
|
||||
}
|
||||
// Append merged record
|
||||
merged_bootstrap_records.push(bsrec);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bsmap)
|
||||
// ensure dial infos are sorted
|
||||
for mbr in &mut merged_bootstrap_records {
|
||||
mbr.dial_info_details.sort();
|
||||
}
|
||||
|
||||
Ok(merged_bootstrap_records)
|
||||
}
|
||||
|
||||
// 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM
|
||||
@ -203,21 +258,20 @@ impl RoutingTable {
|
||||
|
||||
// Got peer info, let's add it to the routing table
|
||||
for pi in peer_info {
|
||||
let k = pi.node_id.key;
|
||||
// Register the node
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
k,
|
||||
pi.signed_node_info,
|
||||
false,
|
||||
) {
|
||||
if let Some(nr) =
|
||||
self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, false)
|
||||
{
|
||||
// Add this our futures to process in parallel
|
||||
let routing_table = self.clone();
|
||||
unord.push(
|
||||
// lets ask bootstrap to find ourselves now
|
||||
async move { routing_table.reverse_find_node(nr, true).await }
|
||||
.instrument(Span::current()),
|
||||
);
|
||||
for crypto_kind in VALID_CRYPTO_KINDS {
|
||||
let routing_table = self.clone();
|
||||
let nr = nr.clone();
|
||||
unord.push(
|
||||
// lets ask bootstrap to find ourselves now
|
||||
async move { routing_table.reverse_find_node(crypto_kind, nr, true).await }
|
||||
.instrument(Span::current()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,112 +284,100 @@ impl RoutingTable {
|
||||
|
||||
#[instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> {
|
||||
let (bootstrap, bootstrap_nodes) = self.with_config(|c| {
|
||||
(
|
||||
c.network.bootstrap.clone(),
|
||||
c.network.bootstrap_nodes.clone(),
|
||||
)
|
||||
});
|
||||
let bootstrap = self
|
||||
.unlocked_inner
|
||||
.with_config(|c| c.network.routing_table.bootstrap.clone());
|
||||
|
||||
// Don't bother if bootstraps aren't configured
|
||||
if bootstrap.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log_rtab!(debug "--- bootstrap_task");
|
||||
|
||||
// Get counts by crypto kind
|
||||
let entry_count = self.inner.read().cached_entry_counts();
|
||||
|
||||
// See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism
|
||||
if !bootstrap.is_empty() && bootstrap_nodes.is_empty() {
|
||||
let mut bootstrap_dialinfos = Vec::<DialInfo>::new();
|
||||
for b in &bootstrap {
|
||||
if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) {
|
||||
for bootstrap_di in bootstrap_di_vec {
|
||||
bootstrap_dialinfos.push(bootstrap_di);
|
||||
}
|
||||
let mut bootstrap_dialinfos = Vec::<DialInfo>::new();
|
||||
for b in &bootstrap {
|
||||
if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) {
|
||||
for bootstrap_di in bootstrap_di_vec {
|
||||
bootstrap_dialinfos.push(bootstrap_di);
|
||||
}
|
||||
}
|
||||
if bootstrap_dialinfos.len() > 0 {
|
||||
return self
|
||||
.direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
if bootstrap_dialinfos.len() > 0 {
|
||||
return self
|
||||
.direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos)
|
||||
.await;
|
||||
}
|
||||
|
||||
// If we aren't specifying a bootstrap node list explicitly, then pull from the bootstrap server(s)
|
||||
let bsmap: BootstrapRecordMap = if !bootstrap_nodes.is_empty() {
|
||||
let mut bsmap = BootstrapRecordMap::new();
|
||||
let mut bootstrap_node_dial_infos = Vec::new();
|
||||
for b in bootstrap_nodes {
|
||||
let (id_str, di_str) = b
|
||||
.split_once('@')
|
||||
.ok_or_else(|| eyre!("Invalid node dial info in bootstrap entry"))?;
|
||||
let node_id =
|
||||
NodeId::from_str(id_str).wrap_err("Invalid node id in bootstrap entry")?;
|
||||
let dial_info =
|
||||
DialInfo::from_str(di_str).wrap_err("Invalid dial info in bootstrap entry")?;
|
||||
bootstrap_node_dial_infos.push((node_id, dial_info));
|
||||
}
|
||||
for (node_id, dial_info) in bootstrap_node_dial_infos {
|
||||
bsmap
|
||||
.entry(node_id.key)
|
||||
.or_insert_with(|| BootstrapRecord {
|
||||
min_version: MIN_CRYPTO_VERSION,
|
||||
max_version: MAX_CRYPTO_VERSION,
|
||||
dial_info_details: Vec::new(),
|
||||
})
|
||||
.dial_info_details
|
||||
.push(DialInfoDetail {
|
||||
dial_info,
|
||||
class: DialInfoClass::Direct, // Bootstraps are always directly reachable
|
||||
});
|
||||
}
|
||||
bsmap
|
||||
} else {
|
||||
// Resolve bootstrap servers and recurse their TXT entries
|
||||
self.resolve_bootstrap(bootstrap).await?
|
||||
};
|
||||
|
||||
// Map all bootstrap entries to a single key with multiple dialinfo
|
||||
// If not direct, resolve bootstrap servers and recurse their TXT entries
|
||||
let bsrecs = self.resolve_bootstrap(bootstrap).await?;
|
||||
|
||||
// Run all bootstrap operations concurrently
|
||||
let mut unord = FuturesUnordered::new();
|
||||
for (k, mut v) in bsmap {
|
||||
// Sort dial info so we get the preferred order correct
|
||||
v.dial_info_details.sort();
|
||||
for bsrec in bsrecs {
|
||||
log_rtab!(
|
||||
"--- bootstrapping {} with {:?}",
|
||||
&bsrec.node_ids,
|
||||
&bsrec.dial_info_details
|
||||
);
|
||||
|
||||
log_rtab!("--- bootstrapping {} with {:?}", k.encode(), &v);
|
||||
// Get crypto support from list of node ids
|
||||
let crypto_support = bsrec.node_ids.kinds();
|
||||
|
||||
// Make invalid signed node info (no signature)
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
k,
|
||||
SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo {
|
||||
network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable
|
||||
outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled
|
||||
address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable
|
||||
min_version: v.min_version, // Minimum crypto version specified in txt record
|
||||
max_version: v.max_version, // Maximum crypto version specified in txt record
|
||||
dial_info_detail_list: v.dial_info_details, // Dial info is as specified in the bootstrap list
|
||||
})),
|
||||
true,
|
||||
) {
|
||||
// Make unsigned SignedNodeInfo
|
||||
let sni = SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo {
|
||||
network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable
|
||||
outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled
|
||||
address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable
|
||||
envelope_support: bsrec.envelope_support, // Envelope support is as specified in the bootstrap list
|
||||
crypto_support, // Crypto support is derived from list of node ids
|
||||
dial_info_detail_list: bsrec.dial_info_details, // Dial info is as specified in the bootstrap list
|
||||
}));
|
||||
|
||||
let pi = PeerInfo::new(bsrec.node_ids, sni);
|
||||
|
||||
if let Some(nr) =
|
||||
self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true)
|
||||
{
|
||||
// Add this our futures to process in parallel
|
||||
let routing_table = self.clone();
|
||||
unord.push(
|
||||
async move {
|
||||
// Need VALID signed peer info, so ask bootstrap to find_node of itself
|
||||
// which will ensure it has the bootstrap's signed peer info as part of the response
|
||||
let _ = routing_table.find_target(nr.clone()).await;
|
||||
|
||||
// Ensure we got the signed peer info
|
||||
if !nr.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) {
|
||||
log_rtab!(warn
|
||||
"bootstrap at {:?} did not return valid signed node info",
|
||||
nr
|
||||
);
|
||||
// If this node info is invalid, it will time out after being unpingable
|
||||
} else {
|
||||
// otherwise this bootstrap is valid, lets ask it to find ourselves now
|
||||
routing_table.reverse_find_node(nr, true).await
|
||||
}
|
||||
for crypto_kind in VALID_CRYPTO_KINDS {
|
||||
// Do we need to bootstrap this crypto kind?
|
||||
let eckey = (RoutingDomain::PublicInternet, crypto_kind);
|
||||
let cnt = entry_count.get(&eckey).copied().unwrap_or_default();
|
||||
if cnt != 0 {
|
||||
continue;
|
||||
}
|
||||
.instrument(Span::current()),
|
||||
);
|
||||
|
||||
// Bootstrap this crypto kind
|
||||
let nr = nr.clone();
|
||||
let routing_table = self.clone();
|
||||
unord.push(
|
||||
async move {
|
||||
// Need VALID signed peer info, so ask bootstrap to find_node of itself
|
||||
// which will ensure it has the bootstrap's signed peer info as part of the response
|
||||
let _ = routing_table.find_target(crypto_kind, nr.clone()).await;
|
||||
|
||||
// Ensure we got the signed peer info
|
||||
if !nr
|
||||
.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet)
|
||||
{
|
||||
log_rtab!(warn
|
||||
"bootstrap at {:?} did not return valid signed node info",
|
||||
nr
|
||||
);
|
||||
// If this node info is invalid, it will time out after being unpingable
|
||||
} else {
|
||||
// otherwise this bootstrap is valid, lets ask it to find ourselves now
|
||||
routing_table.reverse_find_node(crypto_kind, nr, true).await
|
||||
}
|
||||
}
|
||||
.instrument(Span::current()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,13 @@ impl RoutingTable {
|
||||
_last_ts: Timestamp,
|
||||
cur_ts: Timestamp,
|
||||
) -> EyreResult<()> {
|
||||
let kick_queue: Vec<usize> = core::mem::take(&mut *self.unlocked_inner.kick_queue.lock())
|
||||
.into_iter()
|
||||
.collect();
|
||||
let kick_queue: Vec<BucketIndex> =
|
||||
core::mem::take(&mut *self.unlocked_inner.kick_queue.lock())
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut inner = self.inner.write();
|
||||
for idx in kick_queue {
|
||||
inner.kick_bucket(idx)
|
||||
for bucket_index in kick_queue {
|
||||
inner.kick_bucket(bucket_index)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -134,21 +134,30 @@ impl RoutingTable {
|
||||
self.unlocked_inner.kick_buckets_task.tick().await?;
|
||||
}
|
||||
|
||||
// See how many live PublicInternet entries we have
|
||||
let live_public_internet_entry_count = self.get_entry_count(
|
||||
RoutingDomain::PublicInternet.into(),
|
||||
BucketEntryState::Unreliable,
|
||||
);
|
||||
// Refresh entry counts
|
||||
let entry_counts = {
|
||||
let mut inner = self.inner.write();
|
||||
inner.refresh_cached_entry_counts()
|
||||
};
|
||||
|
||||
let min_peer_count = self.with_config(|c| c.network.dht.min_peer_count as usize);
|
||||
|
||||
// If none, then add the bootstrap nodes to it
|
||||
if live_public_internet_entry_count == 0 {
|
||||
// Figure out which tables need bootstrap or peer minimum refresh
|
||||
let mut needs_bootstrap = false;
|
||||
let mut needs_peer_minimum_refresh = false;
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
let eckey = (RoutingDomain::PublicInternet, ck);
|
||||
let cnt = entry_counts.get(&eckey).copied().unwrap_or_default();
|
||||
if cnt == 0 {
|
||||
needs_bootstrap = true;
|
||||
} else if cnt < min_peer_count {
|
||||
needs_peer_minimum_refresh = true;
|
||||
}
|
||||
}
|
||||
if needs_bootstrap {
|
||||
self.unlocked_inner.bootstrap_task.tick().await?;
|
||||
}
|
||||
// If we still don't have enough peers, find nodes until we do
|
||||
else if !self.unlocked_inner.bootstrap_task.is_running()
|
||||
&& live_public_internet_entry_count < min_peer_count
|
||||
{
|
||||
if needs_peer_minimum_refresh {
|
||||
self.unlocked_inner.peer_minimum_refresh_task.tick().await?;
|
||||
}
|
||||
|
||||
|
@ -16,26 +16,60 @@ impl RoutingTable {
|
||||
self,
|
||||
stop_token: StopToken,
|
||||
) -> EyreResult<()> {
|
||||
// Get counts by crypto kind
|
||||
let entry_count = self.inner.read().cached_entry_counts();
|
||||
|
||||
let min_peer_count = self.with_config(|c| c.network.dht.min_peer_count as usize);
|
||||
|
||||
// For the PublicInternet routing domain, get list of all peers we know about
|
||||
// even the unreliable ones, and ask them to find nodes close to our node too
|
||||
let routing_table = self.clone();
|
||||
let noderefs = routing_table.find_fastest_nodes(
|
||||
min_peer_count,
|
||||
VecDeque::new(),
|
||||
|_rti, k: DHTKey, v: Option<Arc<BucketEntry>>| {
|
||||
NodeRef::new(routing_table.clone(), k, v.unwrap().clone(), None)
|
||||
},
|
||||
);
|
||||
|
||||
let mut ord = FuturesOrdered::new();
|
||||
for nr in noderefs {
|
||||
|
||||
for crypto_kind in VALID_CRYPTO_KINDS {
|
||||
// Do we need to peer minimum refresh this crypto kind?
|
||||
let eckey = (RoutingDomain::PublicInternet, crypto_kind);
|
||||
let cnt = entry_count.get(&eckey).copied().unwrap_or_default();
|
||||
if cnt == 0 || cnt > min_peer_count {
|
||||
// If we have enough nodes, skip it
|
||||
// If we have zero nodes, bootstrap will get it
|
||||
continue;
|
||||
}
|
||||
|
||||
let routing_table = self.clone();
|
||||
ord.push_back(
|
||||
async move { routing_table.reverse_find_node(nr, false).await }
|
||||
.instrument(Span::current()),
|
||||
|
||||
let mut filters = VecDeque::new();
|
||||
let filter = Box::new(
|
||||
move |_rti: &RoutingTableInner, opt_entry: Option<Arc<BucketEntry>>| {
|
||||
// Keep only the entries that contain the crypto kind we're looking for
|
||||
if let Some(entry) = opt_entry {
|
||||
entry.with_inner(|e| e.crypto_kinds().contains(&crypto_kind))
|
||||
} else {
|
||||
VALID_CRYPTO_KINDS.contains(&crypto_kind)
|
||||
}
|
||||
},
|
||||
) as RoutingTableEntryFilter;
|
||||
filters.push_front(filter);
|
||||
|
||||
let noderefs = routing_table.find_fastest_nodes(
|
||||
min_peer_count,
|
||||
filters,
|
||||
|_rti, entry: Option<Arc<BucketEntry>>| {
|
||||
NodeRef::new(routing_table.clone(), entry.unwrap().clone(), None)
|
||||
},
|
||||
);
|
||||
|
||||
for nr in noderefs {
|
||||
let routing_table = self.clone();
|
||||
ord.push_back(
|
||||
async move {
|
||||
routing_table
|
||||
.reverse_find_node(crypto_kind, nr, false)
|
||||
.await
|
||||
}
|
||||
.instrument(Span::current()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// do peer minimum search in order from fastest to slowest
|
||||
|
@ -25,7 +25,6 @@ impl RoutingTable {
|
||||
|
||||
// Get the PublicInternet relay if we are using one
|
||||
let opt_relay_nr = self.relay_node(RoutingDomain::PublicInternet);
|
||||
let opt_relay_id = opt_relay_nr.map(|nr| nr.node_id());
|
||||
|
||||
// Get our publicinternet dial info
|
||||
let dids = self.all_filtered_dial_info_details(
|
||||
@ -35,38 +34,42 @@ impl RoutingTable {
|
||||
|
||||
// For all nodes needing pings, figure out how many and over what protocols
|
||||
for nr in node_refs {
|
||||
// If this is a relay, let's check for NAT keepalives
|
||||
// If this is our relay, let's check for NAT keepalives
|
||||
let mut did_pings = false;
|
||||
if Some(nr.node_id()) == opt_relay_id {
|
||||
// Relay nodes get pinged over all protocols we have inbound dialinfo for
|
||||
// This is so we can preserve the inbound NAT mappings at our router
|
||||
for did in &dids {
|
||||
// Do we need to do this ping?
|
||||
// Check if we have already pinged over this low-level-protocol/address-type/port combo
|
||||
// We want to ensure we do the bare minimum required here
|
||||
let pt = did.dial_info.protocol_type();
|
||||
let at = did.dial_info.address_type();
|
||||
let needs_ping = if let Some((llpt, port)) =
|
||||
mapped_port_info.protocol_to_port.get(&(pt, at))
|
||||
{
|
||||
mapped_port_info
|
||||
.low_level_protocol_ports
|
||||
.remove(&(*llpt, at, *port))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if needs_ping {
|
||||
let rpc = rpc.clone();
|
||||
let dif = did.dial_info.make_filter();
|
||||
let nr_filtered =
|
||||
nr.filtered_clone(NodeRefFilter::new().with_dial_info_filter(dif));
|
||||
log_net!("--> Keepalive ping to {:?}", nr_filtered);
|
||||
unord.push(
|
||||
async move { rpc.rpc_call_status(Destination::direct(nr_filtered)).await }
|
||||
if let Some(relay_nr) = &opt_relay_nr {
|
||||
if nr.same_entry(relay_nr) {
|
||||
// Relay nodes get pinged over all protocols we have inbound dialinfo for
|
||||
// This is so we can preserve the inbound NAT mappings at our router
|
||||
for did in &dids {
|
||||
// Do we need to do this ping?
|
||||
// Check if we have already pinged over this low-level-protocol/address-type/port combo
|
||||
// We want to ensure we do the bare minimum required here
|
||||
let pt = did.dial_info.protocol_type();
|
||||
let at = did.dial_info.address_type();
|
||||
let needs_ping = if let Some((llpt, port)) =
|
||||
mapped_port_info.protocol_to_port.get(&(pt, at))
|
||||
{
|
||||
mapped_port_info
|
||||
.low_level_protocol_ports
|
||||
.remove(&(*llpt, at, *port))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if needs_ping {
|
||||
let rpc = rpc.clone();
|
||||
let dif = did.dial_info.make_filter();
|
||||
let nr_filtered =
|
||||
nr.filtered_clone(NodeRefFilter::new().with_dial_info_filter(dif));
|
||||
log_net!("--> Keepalive ping to {:?}", nr_filtered);
|
||||
unord.push(
|
||||
async move {
|
||||
rpc.rpc_call_status(Destination::direct(nr_filtered)).await
|
||||
}
|
||||
.instrument(Span::current())
|
||||
.boxed(),
|
||||
);
|
||||
did_pings = true;
|
||||
);
|
||||
did_pings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ const BACKGROUND_SAFETY_ROUTE_COUNT: usize = 2;
|
||||
|
||||
impl RoutingTable {
|
||||
/// Fastest routes sort
|
||||
fn route_sort_latency_fn(a: &(DHTKey, u64), b: &(DHTKey, u64)) -> cmp::Ordering {
|
||||
fn route_sort_latency_fn(a: &(RouteId, u64), b: &(RouteId, u64)) -> cmp::Ordering {
|
||||
let mut al = a.1;
|
||||
let mut bl = b.1;
|
||||
// Treat zero latency as uncalculated
|
||||
@ -35,14 +35,14 @@ impl RoutingTable {
|
||||
///
|
||||
/// If a route doesn't 'need_testing', then we neither test nor drop it
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
fn get_allocated_routes_to_test(&self, cur_ts: Timestamp) -> Vec<DHTKey> {
|
||||
fn get_allocated_routes_to_test(&self, cur_ts: Timestamp) -> Vec<RouteId> {
|
||||
let default_route_hop_count =
|
||||
self.with_config(|c| c.network.rpc.default_route_hop_count as usize);
|
||||
|
||||
let rss = self.route_spec_store();
|
||||
let mut must_test_routes = Vec::<DHTKey>::new();
|
||||
let mut unpublished_routes = Vec::<(DHTKey, u64)>::new();
|
||||
let mut expired_routes = Vec::<DHTKey>::new();
|
||||
let mut must_test_routes = Vec::<RouteId>::new();
|
||||
let mut unpublished_routes = Vec::<(RouteId, u64)>::new();
|
||||
let mut expired_routes = Vec::<RouteId>::new();
|
||||
rss.list_allocated_routes(|k, v| {
|
||||
let stats = v.get_stats();
|
||||
// Ignore nodes that don't need testing
|
||||
@ -81,7 +81,7 @@ impl RoutingTable {
|
||||
}
|
||||
|
||||
// Process dead routes
|
||||
for r in &expired_routes {
|
||||
for r in expired_routes {
|
||||
log_rtab!(debug "Expired route: {}", r);
|
||||
rss.release_route(r);
|
||||
}
|
||||
@ -95,7 +95,7 @@ impl RoutingTable {
|
||||
async fn test_route_set(
|
||||
&self,
|
||||
stop_token: StopToken,
|
||||
routes_needing_testing: Vec<DHTKey>,
|
||||
routes_needing_testing: Vec<RouteId>,
|
||||
) -> EyreResult<()> {
|
||||
if routes_needing_testing.is_empty() {
|
||||
return Ok(());
|
||||
@ -107,43 +107,45 @@ impl RoutingTable {
|
||||
#[derive(Default, Debug)]
|
||||
struct TestRouteContext {
|
||||
failed: bool,
|
||||
dead_routes: Vec<DHTKey>,
|
||||
dead_routes: Vec<RouteId>,
|
||||
}
|
||||
|
||||
let mut unord = FuturesUnordered::new();
|
||||
let ctx = Arc::new(Mutex::new(TestRouteContext::default()));
|
||||
for r in routes_needing_testing {
|
||||
let rss = rss.clone();
|
||||
let ctx = ctx.clone();
|
||||
unord.push(
|
||||
async move {
|
||||
let success = match rss.test_route(&r).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_rtab!(error "Test route failed: {}", e);
|
||||
ctx.lock().failed = true;
|
||||
{
|
||||
let mut unord = FuturesUnordered::new();
|
||||
for r in routes_needing_testing {
|
||||
let rss = rss.clone();
|
||||
let ctx = ctx.clone();
|
||||
unord.push(
|
||||
async move {
|
||||
let success = match rss.test_route(r).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log_rtab!(error "Test route failed: {}", e);
|
||||
ctx.lock().failed = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
if success {
|
||||
// Route is okay, leave it alone
|
||||
return;
|
||||
}
|
||||
};
|
||||
if success {
|
||||
// Route is okay, leave it alone
|
||||
return;
|
||||
// Route test failed
|
||||
ctx.lock().dead_routes.push(r);
|
||||
}
|
||||
// Route test failed
|
||||
ctx.lock().dead_routes.push(r);
|
||||
}
|
||||
.instrument(Span::current())
|
||||
.boxed(),
|
||||
);
|
||||
.instrument(Span::current())
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for test_route futures to complete in parallel
|
||||
while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {}
|
||||
}
|
||||
|
||||
// Wait for test_route futures to complete in parallel
|
||||
while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {}
|
||||
|
||||
// Process failed routes
|
||||
let ctx = &mut *ctx.lock();
|
||||
for r in &ctx.dead_routes {
|
||||
log_rtab!(debug "Dead route failed to test: {}", &r);
|
||||
let ctx = Arc::try_unwrap(ctx).unwrap().into_inner();
|
||||
for r in ctx.dead_routes {
|
||||
log_rtab!(debug "Dead route failed to test: {}", r);
|
||||
rss.release_route(r);
|
||||
}
|
||||
|
||||
@ -176,13 +178,16 @@ impl RoutingTable {
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Ensure we have a minimum of N allocated local, unpublished routes with the default number of hops
|
||||
// Ensure we have a minimum of N allocated local, unpublished routes with the default number of hops and all our supported crypto kinds
|
||||
let default_route_hop_count =
|
||||
self.with_config(|c| c.network.rpc.default_route_hop_count as usize);
|
||||
let mut local_unpublished_route_count = 0usize;
|
||||
let rss = self.route_spec_store();
|
||||
rss.list_allocated_routes(|_k, v| {
|
||||
if !v.is_published() && v.hop_count() == default_route_hop_count {
|
||||
if !v.is_published()
|
||||
&& v.hop_count() == default_route_hop_count
|
||||
&& v.get_route_set_keys().kinds() == VALID_CRYPTO_KINDS
|
||||
{
|
||||
local_unpublished_route_count += 1;
|
||||
}
|
||||
Option::<()>::None
|
||||
@ -196,6 +201,7 @@ impl RoutingTable {
|
||||
// Parameters here must be the default safety route spec
|
||||
// These will be used by test_remote_route as well
|
||||
if let Some(k) = rss.allocate_route(
|
||||
&VALID_CRYPTO_KINDS,
|
||||
Stability::default(),
|
||||
Sequencing::default(),
|
||||
default_route_hop_count,
|
||||
|
@ -51,10 +51,9 @@ impl RoutingTable {
|
||||
// The outbound relay is the host of the PWA
|
||||
if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await {
|
||||
// Register new outbound relay
|
||||
if let Some(nr) = self.register_node_with_signed_node_info(
|
||||
if let Some(nr) = self.register_node_with_peer_info(
|
||||
RoutingDomain::PublicInternet,
|
||||
outbound_relay_peerinfo.node_id.key,
|
||||
outbound_relay_peerinfo.signed_node_info,
|
||||
outbound_relay_peerinfo,
|
||||
false,
|
||||
) {
|
||||
info!("Outbound relay node selected: {}", nr);
|
||||
|
@ -21,13 +21,9 @@ impl RoutingTable {
|
||||
);
|
||||
|
||||
// Roll all bucket entry transfers
|
||||
let entries: Vec<Arc<BucketEntry>> = inner
|
||||
.buckets
|
||||
.iter()
|
||||
.flat_map(|b| b.entries().map(|(_k, v)| v.clone()))
|
||||
.collect();
|
||||
for v in entries {
|
||||
v.with_mut(inner, |_rti, e| e.roll_transfers(last_ts, cur_ts));
|
||||
let all_entries: Vec<Arc<BucketEntry>> = inner.all_entries.iter().collect();
|
||||
for entry in all_entries {
|
||||
entry.with_mut(inner, |_rti, e| e.roll_transfers(last_ts, cur_ts));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use core::convert::TryInto;
|
||||
|
||||
pub fn decode_dht_key(public_key: &veilid_capnp::key256::Reader) -> DHTKey {
|
||||
pub fn decode_key256(public_key: &veilid_capnp::key256::Reader) -> PublicKey {
|
||||
let u0 = public_key.get_u0().to_be_bytes();
|
||||
let u1 = public_key.get_u1().to_be_bytes();
|
||||
let u2 = public_key.get_u2().to_be_bytes();
|
||||
@ -13,32 +13,28 @@ pub fn decode_dht_key(public_key: &veilid_capnp::key256::Reader) -> DHTKey {
|
||||
x[16..24].copy_from_slice(&u2);
|
||||
x[24..32].copy_from_slice(&u3);
|
||||
|
||||
DHTKey::new(x)
|
||||
PublicKey::new(x)
|
||||
}
|
||||
|
||||
pub fn encode_dht_key(
|
||||
key: &DHTKey,
|
||||
builder: &mut veilid_capnp::key256::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
pub fn encode_key256(key: &PublicKey, builder: &mut veilid_capnp::key256::Builder) {
|
||||
builder.set_u0(u64::from_be_bytes(
|
||||
key.bytes[0..8]
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("slice with incorrect length"))?,
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
builder.set_u1(u64::from_be_bytes(
|
||||
key.bytes[8..16]
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("slice with incorrect length"))?,
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
builder.set_u2(u64::from_be_bytes(
|
||||
key.bytes[16..24]
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("slice with incorrect length"))?,
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
builder.set_u3(u64::from_be_bytes(
|
||||
key.bytes[24..32]
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("slice with incorrect length"))?,
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
Ok(())
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
mod address;
|
||||
mod address_type_set;
|
||||
mod dht_key;
|
||||
mod dht_signature;
|
||||
mod dial_info;
|
||||
mod dial_info_class;
|
||||
mod dial_info_detail;
|
||||
mod key256;
|
||||
mod network_class;
|
||||
mod node_info;
|
||||
mod node_status;
|
||||
@ -16,21 +15,22 @@ mod protocol_type_set;
|
||||
mod sender_info;
|
||||
mod sequencing;
|
||||
mod signal_info;
|
||||
mod signature512;
|
||||
mod signed_direct_node_info;
|
||||
mod signed_node_info;
|
||||
mod signed_relayed_node_info;
|
||||
mod socket_address;
|
||||
mod tunnel;
|
||||
mod typed_key;
|
||||
mod typed_signature;
|
||||
mod value_data;
|
||||
mod value_key;
|
||||
|
||||
pub use address::*;
|
||||
pub use address_type_set::*;
|
||||
pub use dht_key::*;
|
||||
pub use dht_signature::*;
|
||||
pub use dial_info::*;
|
||||
pub use dial_info_class::*;
|
||||
pub use dial_info_detail::*;
|
||||
pub use key256::*;
|
||||
pub use network_class::*;
|
||||
pub use node_info::*;
|
||||
pub use node_status::*;
|
||||
@ -42,12 +42,14 @@ pub use protocol_type_set::*;
|
||||
pub use sender_info::*;
|
||||
pub use sequencing::*;
|
||||
pub use signal_info::*;
|
||||
pub use signature512::*;
|
||||
pub use signed_direct_node_info::*;
|
||||
pub use signed_node_info::*;
|
||||
pub use signed_relayed_node_info::*;
|
||||
pub use socket_address::*;
|
||||
pub use tunnel::*;
|
||||
pub use typed_key::*;
|
||||
pub use typed_signature::*;
|
||||
pub use value_data::*;
|
||||
pub use value_key::*;
|
||||
|
||||
use super::*;
|
||||
|
@ -12,8 +12,24 @@ pub fn encode_node_info(
|
||||
let mut ats_builder = builder.reborrow().init_address_types();
|
||||
encode_address_type_set(&node_info.address_types, &mut ats_builder)?;
|
||||
|
||||
builder.set_min_version(node_info.min_version);
|
||||
builder.set_max_version(node_info.max_version);
|
||||
let mut es_builder = builder
|
||||
.reborrow()
|
||||
.init_envelope_support(node_info.envelope_support.len() as u32);
|
||||
if let Some(s) = es_builder.as_slice() {
|
||||
s.clone_from_slice(&node_info.envelope_support);
|
||||
}
|
||||
|
||||
let mut cs_builder = builder
|
||||
.reborrow()
|
||||
.init_crypto_support(node_info.crypto_support.len() as u32);
|
||||
if let Some(s) = cs_builder.as_slice() {
|
||||
let csvec: Vec<u32> = node_info
|
||||
.crypto_support
|
||||
.iter()
|
||||
.map(|x| u32::from_be_bytes(x.0))
|
||||
.collect();
|
||||
s.clone_from_slice(&csvec);
|
||||
}
|
||||
|
||||
let mut didl_builder = builder.reborrow().init_dial_info_detail_list(
|
||||
node_info
|
||||
@ -55,8 +71,51 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result<Node
|
||||
.map_err(RPCError::protocol)?,
|
||||
)?;
|
||||
|
||||
let min_version = reader.reborrow().get_min_version();
|
||||
let max_version = reader.reborrow().get_max_version();
|
||||
let envelope_support = reader
|
||||
.reborrow()
|
||||
.get_envelope_support()
|
||||
.map_err(RPCError::protocol)?
|
||||
.as_slice()
|
||||
.map(|s| s.to_vec())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Ensure envelope versions are not duplicated
|
||||
// Unsorted is okay, some nodes may have a different envelope order preference
|
||||
// But nothing should show up more than once
|
||||
let mut eversions = envelope_support.clone();
|
||||
eversions.dedup();
|
||||
if eversions.len() != envelope_support.len() {
|
||||
return Err(RPCError::protocol("duplicate envelope versions"));
|
||||
}
|
||||
if envelope_support.len() > MAX_ENVELOPE_VERSIONS {
|
||||
return Err(RPCError::protocol("too many envelope versions"));
|
||||
}
|
||||
if envelope_support.len() == 0 {
|
||||
return Err(RPCError::protocol("no envelope versions"));
|
||||
}
|
||||
|
||||
let crypto_support: Vec<CryptoKind> = reader
|
||||
.reborrow()
|
||||
.get_crypto_support()
|
||||
.map_err(RPCError::protocol)?
|
||||
.as_slice()
|
||||
.map(|s| s.iter().map(|x| FourCC::from(x.to_be_bytes())).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Ensure crypto kinds are not duplicated
|
||||
// Unsorted is okay, some nodes may have a different crypto order preference
|
||||
// But nothing should show up more than once
|
||||
let mut ckinds = crypto_support.clone();
|
||||
ckinds.dedup();
|
||||
if ckinds.len() != crypto_support.len() {
|
||||
return Err(RPCError::protocol("duplicate crypto kinds"));
|
||||
}
|
||||
if crypto_support.len() > MAX_CRYPTO_KINDS {
|
||||
return Err(RPCError::protocol("too many crypto kinds"));
|
||||
}
|
||||
if crypto_support.len() == 0 {
|
||||
return Err(RPCError::protocol("no crypto kinds"));
|
||||
}
|
||||
|
||||
let didl_reader = reader
|
||||
.reborrow()
|
||||
@ -76,8 +135,8 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result<Node
|
||||
network_class,
|
||||
outbound_protocols,
|
||||
address_types,
|
||||
min_version,
|
||||
max_version,
|
||||
envelope_support,
|
||||
crypto_support,
|
||||
dial_info_detail_list,
|
||||
})
|
||||
}
|
||||
|
@ -2,15 +2,17 @@ use super::*;
|
||||
|
||||
pub fn encode_nonce(nonce: &Nonce, builder: &mut veilid_capnp::nonce24::Builder) {
|
||||
builder.set_u0(u64::from_be_bytes(
|
||||
nonce[0..8].try_into().expect("slice with incorrect length"),
|
||||
nonce.bytes[0..8]
|
||||
.try_into()
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
builder.set_u1(u64::from_be_bytes(
|
||||
nonce[8..16]
|
||||
nonce.bytes[8..16]
|
||||
.try_into()
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
builder.set_u2(u64::from_be_bytes(
|
||||
nonce[16..24]
|
||||
nonce.bytes[16..24]
|
||||
.try_into()
|
||||
.expect("slice with incorrect length"),
|
||||
));
|
||||
@ -21,9 +23,9 @@ pub fn decode_nonce(reader: &veilid_capnp::nonce24::Reader) -> Nonce {
|
||||
let u1 = reader.get_u1().to_be_bytes();
|
||||
let u2 = reader.get_u2().to_be_bytes();
|
||||
|
||||
[
|
||||
Nonce::new([
|
||||
u0[0], u0[1], u0[2], u0[3], u0[4], u0[5], u0[6], u0[7], // u0
|
||||
u1[0], u1[1], u1[2], u1[3], u1[4], u1[5], u1[6], u1[7], // u1
|
||||
u2[0], u2[1], u2[2], u2[3], u2[4], u2[5], u2[6], u2[7], // u2
|
||||
]
|
||||
])
|
||||
}
|
||||
|
@ -15,9 +15,12 @@ impl RPCAnswer {
|
||||
pub fn desc(&self) -> &'static str {
|
||||
self.detail.desc()
|
||||
}
|
||||
pub fn decode(reader: &veilid_capnp::answer::Reader) -> Result<RPCAnswer, RPCError> {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::answer::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCAnswer, RPCError> {
|
||||
let d_reader = reader.get_detail();
|
||||
let detail = RPCAnswerDetail::decode(&d_reader)?;
|
||||
let detail = RPCAnswerDetail::decode(&d_reader, crypto)?;
|
||||
Ok(RPCAnswer { detail })
|
||||
}
|
||||
pub fn encode(&self, builder: &mut veilid_capnp::answer::Builder) -> Result<(), RPCError> {
|
||||
@ -60,6 +63,7 @@ impl RPCAnswerDetail {
|
||||
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::answer::detail::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCAnswerDetail, RPCError> {
|
||||
let which_reader = reader.which().map_err(RPCError::protocol)?;
|
||||
let out = match which_reader {
|
||||
@ -70,7 +74,7 @@ impl RPCAnswerDetail {
|
||||
}
|
||||
veilid_capnp::answer::detail::FindNodeA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationFindNodeA::decode(&op_reader)?;
|
||||
let out = RPCOperationFindNodeA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::FindNodeA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::AppCallA(r) => {
|
||||
@ -80,27 +84,27 @@ impl RPCAnswerDetail {
|
||||
}
|
||||
veilid_capnp::answer::detail::GetValueA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationGetValueA::decode(&op_reader)?;
|
||||
let out = RPCOperationGetValueA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::GetValueA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::SetValueA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationSetValueA::decode(&op_reader)?;
|
||||
let out = RPCOperationSetValueA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::SetValueA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::WatchValueA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationWatchValueA::decode(&op_reader)?;
|
||||
let out = RPCOperationWatchValueA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::WatchValueA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::SupplyBlockA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationSupplyBlockA::decode(&op_reader)?;
|
||||
let out = RPCOperationSupplyBlockA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::SupplyBlockA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::FindBlockA(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationFindBlockA::decode(&op_reader)?;
|
||||
let out = RPCOperationFindBlockA::decode(&op_reader, crypto)?;
|
||||
RPCAnswerDetail::FindBlockA(out)
|
||||
}
|
||||
veilid_capnp::answer::detail::StartTunnelA(r) => {
|
||||
|
@ -16,22 +16,25 @@ impl RPCOperationKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(kind_reader: &veilid_capnp::operation::kind::Reader) -> Result<Self, RPCError> {
|
||||
pub fn decode(
|
||||
kind_reader: &veilid_capnp::operation::kind::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<Self, RPCError> {
|
||||
let which_reader = kind_reader.which().map_err(RPCError::protocol)?;
|
||||
let out = match which_reader {
|
||||
veilid_capnp::operation::kind::Which::Question(r) => {
|
||||
let q_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCQuestion::decode(&q_reader)?;
|
||||
let out = RPCQuestion::decode(&q_reader, crypto)?;
|
||||
RPCOperationKind::Question(out)
|
||||
}
|
||||
veilid_capnp::operation::kind::Which::Statement(r) => {
|
||||
let q_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCStatement::decode(&q_reader)?;
|
||||
let out = RPCStatement::decode(&q_reader, crypto)?;
|
||||
RPCOperationKind::Statement(out)
|
||||
}
|
||||
veilid_capnp::operation::kind::Which::Answer(r) => {
|
||||
let q_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCAnswer::decode(&q_reader)?;
|
||||
let out = RPCAnswer::decode(&q_reader, crypto)?;
|
||||
RPCOperationKind::Answer(out)
|
||||
}
|
||||
};
|
||||
@ -54,31 +57,25 @@ impl RPCOperationKind {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperation {
|
||||
op_id: OperationId,
|
||||
sender_node_info: Option<SignedNodeInfo>,
|
||||
opt_sender_peer_info: Option<PeerInfo>,
|
||||
target_node_info_ts: Timestamp,
|
||||
kind: RPCOperationKind,
|
||||
}
|
||||
|
||||
impl RPCOperation {
|
||||
pub fn new_question(
|
||||
question: RPCQuestion,
|
||||
sender_signed_node_info: SenderSignedNodeInfo,
|
||||
) -> Self {
|
||||
pub fn new_question(question: RPCQuestion, sender_peer_info: SenderPeerInfo) -> Self {
|
||||
Self {
|
||||
op_id: OperationId::new(get_random_u64()),
|
||||
sender_node_info: sender_signed_node_info.signed_node_info,
|
||||
target_node_info_ts: sender_signed_node_info.target_node_info_ts,
|
||||
opt_sender_peer_info: sender_peer_info.opt_sender_peer_info,
|
||||
target_node_info_ts: sender_peer_info.target_node_info_ts,
|
||||
kind: RPCOperationKind::Question(question),
|
||||
}
|
||||
}
|
||||
pub fn new_statement(
|
||||
statement: RPCStatement,
|
||||
sender_signed_node_info: SenderSignedNodeInfo,
|
||||
) -> Self {
|
||||
pub fn new_statement(statement: RPCStatement, sender_peer_info: SenderPeerInfo) -> Self {
|
||||
Self {
|
||||
op_id: OperationId::new(get_random_u64()),
|
||||
sender_node_info: sender_signed_node_info.signed_node_info,
|
||||
target_node_info_ts: sender_signed_node_info.target_node_info_ts,
|
||||
opt_sender_peer_info: sender_peer_info.opt_sender_peer_info,
|
||||
target_node_info_ts: sender_peer_info.target_node_info_ts,
|
||||
kind: RPCOperationKind::Statement(statement),
|
||||
}
|
||||
}
|
||||
@ -86,12 +83,12 @@ impl RPCOperation {
|
||||
pub fn new_answer(
|
||||
request: &RPCOperation,
|
||||
answer: RPCAnswer,
|
||||
sender_signed_node_info: SenderSignedNodeInfo,
|
||||
sender_peer_info: SenderPeerInfo,
|
||||
) -> Self {
|
||||
Self {
|
||||
op_id: request.op_id,
|
||||
sender_node_info: sender_signed_node_info.signed_node_info,
|
||||
target_node_info_ts: sender_signed_node_info.target_node_info_ts,
|
||||
opt_sender_peer_info: sender_peer_info.opt_sender_peer_info,
|
||||
target_node_info_ts: sender_peer_info.target_node_info_ts,
|
||||
kind: RPCOperationKind::Answer(answer),
|
||||
}
|
||||
}
|
||||
@ -100,8 +97,8 @@ impl RPCOperation {
|
||||
self.op_id
|
||||
}
|
||||
|
||||
pub fn sender_node_info(&self) -> Option<&SignedNodeInfo> {
|
||||
self.sender_node_info.as_ref()
|
||||
pub fn sender_peer_info(&self) -> Option<&PeerInfo> {
|
||||
self.opt_sender_peer_info.as_ref()
|
||||
}
|
||||
pub fn target_node_info_ts(&self) -> Timestamp {
|
||||
self.target_node_info_ts
|
||||
@ -117,20 +114,16 @@ impl RPCOperation {
|
||||
|
||||
pub fn decode(
|
||||
operation_reader: &veilid_capnp::operation::Reader,
|
||||
opt_sender_node_id: Option<&DHTKey>,
|
||||
crypto: Crypto,
|
||||
) -> Result<Self, RPCError> {
|
||||
let op_id = OperationId::new(operation_reader.get_op_id());
|
||||
|
||||
let sender_node_info = if operation_reader.has_sender_node_info() {
|
||||
if let Some(sender_node_id) = opt_sender_node_id {
|
||||
let sni_reader = operation_reader
|
||||
.get_sender_node_info()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let sni = decode_signed_node_info(&sni_reader, sender_node_id)?;
|
||||
Some(sni)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let sender_peer_info = if operation_reader.has_sender_peer_info() {
|
||||
let pi_reader = operation_reader
|
||||
.get_sender_peer_info()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let pi = decode_peer_info(&pi_reader, crypto.clone())?;
|
||||
Some(pi)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -138,11 +131,11 @@ impl RPCOperation {
|
||||
let target_node_info_ts = Timestamp::new(operation_reader.get_target_node_info_ts());
|
||||
|
||||
let kind_reader = operation_reader.get_kind();
|
||||
let kind = RPCOperationKind::decode(&kind_reader)?;
|
||||
let kind = RPCOperationKind::decode(&kind_reader, crypto)?;
|
||||
|
||||
Ok(RPCOperation {
|
||||
op_id,
|
||||
sender_node_info,
|
||||
opt_sender_peer_info: sender_peer_info,
|
||||
target_node_info_ts,
|
||||
kind,
|
||||
})
|
||||
@ -150,9 +143,9 @@ impl RPCOperation {
|
||||
|
||||
pub fn encode(&self, builder: &mut veilid_capnp::operation::Builder) -> Result<(), RPCError> {
|
||||
builder.set_op_id(self.op_id.as_u64());
|
||||
if let Some(sender_info) = &self.sender_node_info {
|
||||
let mut si_builder = builder.reborrow().init_sender_node_info();
|
||||
encode_signed_node_info(&sender_info, &mut si_builder)?;
|
||||
if let Some(sender_peer_info) = &self.opt_sender_peer_info {
|
||||
let mut pi_builder = builder.reborrow().init_sender_peer_info();
|
||||
encode_peer_info(&sender_peer_info, &mut pi_builder)?;
|
||||
}
|
||||
builder.set_target_node_info_ts(self.target_node_info_ts.as_u64());
|
||||
let mut k_builder = builder.reborrow().init_kind();
|
||||
|
@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationFindBlockQ {
|
||||
pub block_id: DHTKey,
|
||||
pub block_id: TypedKey,
|
||||
}
|
||||
|
||||
impl RPCOperationFindBlockQ {
|
||||
@ -10,7 +10,7 @@ impl RPCOperationFindBlockQ {
|
||||
reader: &veilid_capnp::operation_find_block_q::Reader,
|
||||
) -> Result<RPCOperationFindBlockQ, RPCError> {
|
||||
let bi_reader = reader.get_block_id().map_err(RPCError::protocol)?;
|
||||
let block_id = decode_dht_key(&bi_reader);
|
||||
let block_id = decode_typed_key(&bi_reader)?;
|
||||
|
||||
Ok(RPCOperationFindBlockQ { block_id })
|
||||
}
|
||||
@ -19,7 +19,7 @@ impl RPCOperationFindBlockQ {
|
||||
builder: &mut veilid_capnp::operation_find_block_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut bi_builder = builder.reborrow().init_block_id();
|
||||
encode_dht_key(&self.block_id, &mut bi_builder)?;
|
||||
encode_typed_key(&self.block_id, &mut bi_builder);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -35,6 +35,7 @@ pub struct RPCOperationFindBlockA {
|
||||
impl RPCOperationFindBlockA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_find_block_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationFindBlockA, RPCError> {
|
||||
let data = reader.get_data().map_err(RPCError::protocol)?.to_vec();
|
||||
|
||||
@ -46,7 +47,7 @@ impl RPCOperationFindBlockA {
|
||||
.map_err(RPCError::map_internal("too many suppliers"))?,
|
||||
);
|
||||
for s in suppliers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&s)?;
|
||||
let peer_info = decode_peer_info(&s, crypto.clone())?;
|
||||
suppliers.push(peer_info);
|
||||
}
|
||||
|
||||
@ -58,7 +59,7 @@ impl RPCOperationFindBlockA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationFindNodeQ {
|
||||
pub node_id: DHTKey,
|
||||
pub node_id: TypedKey,
|
||||
}
|
||||
|
||||
impl RPCOperationFindNodeQ {
|
||||
@ -10,7 +10,7 @@ impl RPCOperationFindNodeQ {
|
||||
reader: &veilid_capnp::operation_find_node_q::Reader,
|
||||
) -> Result<RPCOperationFindNodeQ, RPCError> {
|
||||
let ni_reader = reader.get_node_id().map_err(RPCError::protocol)?;
|
||||
let node_id = decode_dht_key(&ni_reader);
|
||||
let node_id = decode_typed_key(&ni_reader)?;
|
||||
Ok(RPCOperationFindNodeQ { node_id })
|
||||
}
|
||||
pub fn encode(
|
||||
@ -18,7 +18,7 @@ impl RPCOperationFindNodeQ {
|
||||
builder: &mut veilid_capnp::operation_find_node_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut ni_builder = builder.reborrow().init_node_id();
|
||||
encode_dht_key(&self.node_id, &mut ni_builder)?;
|
||||
encode_typed_key(&self.node_id, &mut ni_builder);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,7 @@ pub struct RPCOperationFindNodeA {
|
||||
impl RPCOperationFindNodeA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_find_node_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationFindNodeA, RPCError> {
|
||||
let peers_reader = reader.get_peers().map_err(RPCError::protocol)?;
|
||||
let mut peers = Vec::<PeerInfo>::with_capacity(
|
||||
@ -40,7 +41,7 @@ impl RPCOperationFindNodeA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,8 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationGetValueQ {
|
||||
pub key: ValueKey,
|
||||
pub key: TypedKey,
|
||||
pub subkey: ValueSubkey,
|
||||
}
|
||||
|
||||
impl RPCOperationGetValueQ {
|
||||
@ -10,15 +11,17 @@ impl RPCOperationGetValueQ {
|
||||
reader: &veilid_capnp::operation_get_value_q::Reader,
|
||||
) -> Result<RPCOperationGetValueQ, RPCError> {
|
||||
let k_reader = reader.get_key().map_err(RPCError::protocol)?;
|
||||
let key = decode_value_key(&k_reader)?;
|
||||
Ok(RPCOperationGetValueQ { key })
|
||||
let key = decode_typed_key(&k_reader)?;
|
||||
let subkey = reader.get_subkey();
|
||||
Ok(RPCOperationGetValueQ { key, subkey })
|
||||
}
|
||||
pub fn encode(
|
||||
&self,
|
||||
builder: &mut veilid_capnp::operation_get_value_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut k_builder = builder.reborrow().init_key();
|
||||
encode_value_key(&self.key, &mut k_builder)?;
|
||||
encode_typed_key(&self.key, &mut k_builder);
|
||||
builder.set_subkey(self.subkey);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -32,6 +35,7 @@ pub enum RPCOperationGetValueA {
|
||||
impl RPCOperationGetValueA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_get_value_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationGetValueA, RPCError> {
|
||||
match reader.which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::operation_get_value_a::Which::Data(r) => {
|
||||
@ -47,7 +51,7 @@ impl RPCOperationGetValueA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,15 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoutedOperation {
|
||||
pub version: u8,
|
||||
pub sequencing: Sequencing,
|
||||
pub signatures: Vec<DHTSignature>,
|
||||
pub signatures: Vec<Signature>,
|
||||
pub nonce: Nonce,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl RoutedOperation {
|
||||
pub fn new(version: u8, sequencing: Sequencing, nonce: Nonce, data: Vec<u8>) -> Self {
|
||||
pub fn new(sequencing: Sequencing, nonce: Nonce, data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
version,
|
||||
sequencing,
|
||||
signatures: Vec::new(),
|
||||
nonce,
|
||||
@ -24,25 +22,23 @@ impl RoutedOperation {
|
||||
reader: &veilid_capnp::routed_operation::Reader,
|
||||
) -> Result<RoutedOperation, RPCError> {
|
||||
let sigs_reader = reader.get_signatures().map_err(RPCError::protocol)?;
|
||||
let mut signatures = Vec::<DHTSignature>::with_capacity(
|
||||
let mut signatures = Vec::<Signature>::with_capacity(
|
||||
sigs_reader
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_internal("too many signatures"))?,
|
||||
);
|
||||
for s in sigs_reader.iter() {
|
||||
let sig = decode_signature(&s);
|
||||
let sig = decode_signature512(&s);
|
||||
signatures.push(sig);
|
||||
}
|
||||
|
||||
let version = reader.get_version();
|
||||
let sequencing = decode_sequencing(reader.get_sequencing().map_err(RPCError::protocol)?);
|
||||
let n_reader = reader.get_nonce().map_err(RPCError::protocol)?;
|
||||
let nonce = decode_nonce(&n_reader);
|
||||
let data = reader.get_data().map_err(RPCError::protocol)?.to_vec();
|
||||
|
||||
Ok(RoutedOperation {
|
||||
version,
|
||||
sequencing,
|
||||
signatures,
|
||||
nonce,
|
||||
@ -54,7 +50,6 @@ impl RoutedOperation {
|
||||
&self,
|
||||
builder: &mut veilid_capnp::routed_operation::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
builder.reborrow().set_version(self.version);
|
||||
builder
|
||||
.reborrow()
|
||||
.set_sequencing(encode_sequencing(self.sequencing));
|
||||
@ -66,7 +61,7 @@ impl RoutedOperation {
|
||||
);
|
||||
for (i, sig) in self.signatures.iter().enumerate() {
|
||||
let mut sig_builder = sigs_builder.reborrow().get(i as u32);
|
||||
encode_signature(sig, &mut sig_builder);
|
||||
encode_signature512(sig, &mut sig_builder);
|
||||
}
|
||||
let mut n_builder = builder.reborrow().init_nonce();
|
||||
encode_nonce(&self.nonce, &mut n_builder);
|
||||
@ -85,9 +80,10 @@ pub struct RPCOperationRoute {
|
||||
impl RPCOperationRoute {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_route::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationRoute, RPCError> {
|
||||
let sr_reader = reader.get_safety_route().map_err(RPCError::protocol)?;
|
||||
let safety_route = decode_safety_route(&sr_reader)?;
|
||||
let safety_route = decode_safety_route(&sr_reader, crypto)?;
|
||||
|
||||
let o_reader = reader.get_operation().map_err(RPCError::protocol)?;
|
||||
let operation = RoutedOperation::decode(&o_reader)?;
|
||||
|
@ -2,7 +2,8 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationSetValueQ {
|
||||
pub key: ValueKey,
|
||||
pub key: TypedKey,
|
||||
pub subkey: ValueSubkey,
|
||||
pub value: ValueData,
|
||||
}
|
||||
|
||||
@ -11,17 +12,19 @@ impl RPCOperationSetValueQ {
|
||||
reader: &veilid_capnp::operation_set_value_q::Reader,
|
||||
) -> Result<RPCOperationSetValueQ, RPCError> {
|
||||
let k_reader = reader.get_key().map_err(RPCError::protocol)?;
|
||||
let key = decode_value_key(&k_reader)?;
|
||||
let key = decode_typed_key(&k_reader)?;
|
||||
let subkey = reader.get_subkey();
|
||||
let v_reader = reader.get_value().map_err(RPCError::protocol)?;
|
||||
let value = decode_value_data(&v_reader)?;
|
||||
Ok(RPCOperationSetValueQ { key, value })
|
||||
Ok(RPCOperationSetValueQ { key, subkey, value })
|
||||
}
|
||||
pub fn encode(
|
||||
&self,
|
||||
builder: &mut veilid_capnp::operation_set_value_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut k_builder = builder.reborrow().init_key();
|
||||
encode_value_key(&self.key, &mut k_builder)?;
|
||||
encode_typed_key(&self.key, &mut k_builder);
|
||||
builder.set_subkey(self.subkey);
|
||||
let mut v_builder = builder.reborrow().init_value();
|
||||
encode_value_data(&self.value, &mut v_builder)?;
|
||||
Ok(())
|
||||
@ -37,6 +40,7 @@ pub enum RPCOperationSetValueA {
|
||||
impl RPCOperationSetValueA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_set_value_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationSetValueA, RPCError> {
|
||||
match reader.which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::operation_set_value_a::Which::Data(r) => {
|
||||
@ -52,7 +56,7 @@ impl RPCOperationSetValueA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,9 @@ pub struct RPCOperationSignal {
|
||||
impl RPCOperationSignal {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_signal::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationSignal, RPCError> {
|
||||
let signal_info = decode_signal_info(reader)?;
|
||||
let signal_info = decode_signal_info(reader, crypto)?;
|
||||
Ok(RPCOperationSignal { signal_info })
|
||||
}
|
||||
pub fn encode(
|
||||
|
@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationSupplyBlockQ {
|
||||
pub block_id: DHTKey,
|
||||
pub block_id: TypedKey,
|
||||
}
|
||||
|
||||
impl RPCOperationSupplyBlockQ {
|
||||
@ -10,7 +10,7 @@ impl RPCOperationSupplyBlockQ {
|
||||
reader: &veilid_capnp::operation_supply_block_q::Reader,
|
||||
) -> Result<RPCOperationSupplyBlockQ, RPCError> {
|
||||
let bi_reader = reader.get_block_id().map_err(RPCError::protocol)?;
|
||||
let block_id = decode_dht_key(&bi_reader);
|
||||
let block_id = decode_typed_key(&bi_reader)?;
|
||||
|
||||
Ok(RPCOperationSupplyBlockQ { block_id })
|
||||
}
|
||||
@ -19,7 +19,7 @@ impl RPCOperationSupplyBlockQ {
|
||||
builder: &mut veilid_capnp::operation_supply_block_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut bi_builder = builder.reborrow().init_block_id();
|
||||
encode_dht_key(&self.block_id, &mut bi_builder)?;
|
||||
encode_typed_key(&self.block_id, &mut bi_builder);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -34,6 +34,7 @@ pub enum RPCOperationSupplyBlockA {
|
||||
impl RPCOperationSupplyBlockA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_supply_block_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationSupplyBlockA, RPCError> {
|
||||
match reader.which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::operation_supply_block_a::Which::Expiration(r) => {
|
||||
@ -48,7 +49,7 @@ impl RPCOperationSupplyBlockA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,9 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationValueChanged {
|
||||
pub key: ValueKey,
|
||||
pub key: TypedKey,
|
||||
pub subkeys: Vec<ValueSubkeyRange>,
|
||||
pub count: u32,
|
||||
pub value: ValueData,
|
||||
}
|
||||
|
||||
@ -11,17 +13,60 @@ impl RPCOperationValueChanged {
|
||||
reader: &veilid_capnp::operation_value_changed::Reader,
|
||||
) -> Result<RPCOperationValueChanged, RPCError> {
|
||||
let k_reader = reader.get_key().map_err(RPCError::protocol)?;
|
||||
let key = decode_value_key(&k_reader)?;
|
||||
let key = decode_typed_key(&k_reader)?;
|
||||
|
||||
let sk_reader = reader.get_subkeys().map_err(RPCError::protocol)?;
|
||||
let mut subkeys = Vec::<ValueSubkeyRange>::with_capacity(
|
||||
sk_reader
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("too many subkey ranges"))?,
|
||||
);
|
||||
for skr in sk_reader.iter() {
|
||||
let vskr = (skr.get_start(), skr.get_end());
|
||||
if vskr.0 > vskr.1 {
|
||||
return Err(RPCError::protocol("invalid subkey range"));
|
||||
}
|
||||
if let Some(lvskr) = subkeys.last() {
|
||||
if lvskr.1 >= vskr.0 {
|
||||
return Err(RPCError::protocol(
|
||||
"subkey range out of order or not merged",
|
||||
));
|
||||
}
|
||||
}
|
||||
subkeys.push(vskr);
|
||||
}
|
||||
let count = reader.get_count();
|
||||
let v_reader = reader.get_value().map_err(RPCError::protocol)?;
|
||||
let value = decode_value_data(&v_reader)?;
|
||||
Ok(RPCOperationValueChanged { key, value })
|
||||
Ok(RPCOperationValueChanged {
|
||||
key,
|
||||
subkeys,
|
||||
count,
|
||||
value,
|
||||
})
|
||||
}
|
||||
pub fn encode(
|
||||
&self,
|
||||
builder: &mut veilid_capnp::operation_value_changed::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut k_builder = builder.reborrow().init_key();
|
||||
encode_value_key(&self.key, &mut k_builder)?;
|
||||
encode_typed_key(&self.key, &mut k_builder);
|
||||
|
||||
let mut sk_builder = builder.reborrow().init_subkeys(
|
||||
self.subkeys
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_internal("invalid subkey range list length"))?,
|
||||
);
|
||||
for (i, skr) in self.subkeys.iter().enumerate() {
|
||||
let mut skr_builder = sk_builder.reborrow().get(i as u32);
|
||||
skr_builder.set_start(skr.0);
|
||||
skr_builder.set_end(skr.1);
|
||||
}
|
||||
|
||||
builder.set_count(self.count);
|
||||
|
||||
let mut v_builder = builder.reborrow().init_value();
|
||||
encode_value_data(&self.value, &mut v_builder)?;
|
||||
Ok(())
|
||||
|
@ -2,7 +2,10 @@ use super::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCOperationWatchValueQ {
|
||||
pub key: ValueKey,
|
||||
pub key: TypedKey,
|
||||
pub subkeys: Vec<ValueSubkeyRange>,
|
||||
pub expiration: u64,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
impl RPCOperationWatchValueQ {
|
||||
@ -10,15 +13,60 @@ impl RPCOperationWatchValueQ {
|
||||
reader: &veilid_capnp::operation_watch_value_q::Reader,
|
||||
) -> Result<RPCOperationWatchValueQ, RPCError> {
|
||||
let k_reader = reader.get_key().map_err(RPCError::protocol)?;
|
||||
let key = decode_value_key(&k_reader)?;
|
||||
Ok(RPCOperationWatchValueQ { key })
|
||||
let key = decode_typed_key(&k_reader)?;
|
||||
|
||||
let sk_reader = reader.get_subkeys().map_err(RPCError::protocol)?;
|
||||
let mut subkeys = Vec::<ValueSubkeyRange>::with_capacity(
|
||||
sk_reader
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_protocol("too many subkey ranges"))?,
|
||||
);
|
||||
for skr in sk_reader.iter() {
|
||||
let vskr = (skr.get_start(), skr.get_end());
|
||||
if vskr.0 > vskr.1 {
|
||||
return Err(RPCError::protocol("invalid subkey range"));
|
||||
}
|
||||
if let Some(lvskr) = subkeys.last() {
|
||||
if lvskr.1 >= vskr.0 {
|
||||
return Err(RPCError::protocol(
|
||||
"subkey range out of order or not merged",
|
||||
));
|
||||
}
|
||||
}
|
||||
subkeys.push(vskr);
|
||||
}
|
||||
|
||||
let expiration = reader.get_expiration();
|
||||
let count = reader.get_count();
|
||||
|
||||
Ok(RPCOperationWatchValueQ {
|
||||
key,
|
||||
subkeys,
|
||||
expiration,
|
||||
count,
|
||||
})
|
||||
}
|
||||
pub fn encode(
|
||||
&self,
|
||||
builder: &mut veilid_capnp::operation_watch_value_q::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut k_builder = builder.reborrow().init_key();
|
||||
encode_value_key(&self.key, &mut k_builder)?;
|
||||
encode_typed_key(&self.key, &mut k_builder);
|
||||
|
||||
let mut sk_builder = builder.reborrow().init_subkeys(
|
||||
self.subkeys
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_internal("invalid subkey range list length"))?,
|
||||
);
|
||||
for (i, skr) in self.subkeys.iter().enumerate() {
|
||||
let mut skr_builder = sk_builder.reborrow().get(i as u32);
|
||||
skr_builder.set_start(skr.0);
|
||||
skr_builder.set_end(skr.1);
|
||||
}
|
||||
builder.set_expiration(self.expiration);
|
||||
builder.set_count(self.count);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -32,6 +80,7 @@ pub struct RPCOperationWatchValueA {
|
||||
impl RPCOperationWatchValueA {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::operation_watch_value_a::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCOperationWatchValueA, RPCError> {
|
||||
let expiration = reader.get_expiration();
|
||||
let peers_reader = reader.get_peers().map_err(RPCError::protocol)?;
|
||||
@ -42,7 +91,7 @@ impl RPCOperationWatchValueA {
|
||||
.map_err(RPCError::map_internal("too many peers"))?,
|
||||
);
|
||||
for p in peers_reader.iter() {
|
||||
let peer_info = decode_peer_info(&p)?;
|
||||
let peer_info = decode_peer_info(&p, crypto.clone())?;
|
||||
peers.push(peer_info);
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,12 @@ impl RPCQuestion {
|
||||
pub fn desc(&self) -> &'static str {
|
||||
self.detail.desc()
|
||||
}
|
||||
pub fn decode(reader: &veilid_capnp::question::Reader) -> Result<RPCQuestion, RPCError> {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::question::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCQuestion, RPCError> {
|
||||
let rt_reader = reader.get_respond_to();
|
||||
let respond_to = RespondTo::decode(&rt_reader)?;
|
||||
let respond_to = RespondTo::decode(&rt_reader, crypto)?;
|
||||
let d_reader = reader.get_detail();
|
||||
let detail = RPCQuestionDetail::decode(&d_reader)?;
|
||||
Ok(RPCQuestion { respond_to, detail })
|
||||
|
@ -23,12 +23,15 @@ impl RespondTo {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode(reader: &veilid_capnp::question::respond_to::Reader) -> Result<Self, RPCError> {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::question::respond_to::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<Self, RPCError> {
|
||||
let respond_to = match reader.which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::question::respond_to::Sender(()) => RespondTo::Sender,
|
||||
veilid_capnp::question::respond_to::PrivateRoute(pr_reader) => {
|
||||
let pr_reader = pr_reader.map_err(RPCError::protocol)?;
|
||||
let pr = decode_private_route(&pr_reader)?;
|
||||
let pr = decode_private_route(&pr_reader, crypto)?;
|
||||
RespondTo::PrivateRoute(pr)
|
||||
}
|
||||
};
|
||||
|
@ -18,9 +18,12 @@ impl RPCStatement {
|
||||
pub fn desc(&self) -> &'static str {
|
||||
self.detail.desc()
|
||||
}
|
||||
pub fn decode(reader: &veilid_capnp::statement::Reader) -> Result<RPCStatement, RPCError> {
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::statement::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCStatement, RPCError> {
|
||||
let d_reader = reader.get_detail();
|
||||
let detail = RPCStatementDetail::decode(&d_reader)?;
|
||||
let detail = RPCStatementDetail::decode(&d_reader, crypto)?;
|
||||
Ok(RPCStatement { detail })
|
||||
}
|
||||
pub fn encode(&self, builder: &mut veilid_capnp::statement::Builder) -> Result<(), RPCError> {
|
||||
@ -52,6 +55,7 @@ impl RPCStatementDetail {
|
||||
}
|
||||
pub fn decode(
|
||||
reader: &veilid_capnp::statement::detail::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RPCStatementDetail, RPCError> {
|
||||
let which_reader = reader.which().map_err(RPCError::protocol)?;
|
||||
let out = match which_reader {
|
||||
@ -62,7 +66,7 @@ impl RPCStatementDetail {
|
||||
}
|
||||
veilid_capnp::statement::detail::Route(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationRoute::decode(&op_reader)?;
|
||||
let out = RPCOperationRoute::decode(&op_reader, crypto)?;
|
||||
RPCStatementDetail::Route(out)
|
||||
}
|
||||
veilid_capnp::statement::detail::ValueChanged(r) => {
|
||||
@ -72,7 +76,7 @@ impl RPCStatementDetail {
|
||||
}
|
||||
veilid_capnp::statement::detail::Signal(r) => {
|
||||
let op_reader = r.map_err(RPCError::protocol)?;
|
||||
let out = RPCOperationSignal::decode(&op_reader)?;
|
||||
let out = RPCOperationSignal::decode(&op_reader, crypto)?;
|
||||
RPCStatementDetail::Signal(out)
|
||||
}
|
||||
veilid_capnp::statement::detail::ReturnReceipt(r) => {
|
||||
|
@ -5,28 +5,50 @@ pub fn encode_peer_info(
|
||||
builder: &mut veilid_capnp::peer_info::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
//
|
||||
let mut nid_builder = builder.reborrow().init_node_id();
|
||||
encode_dht_key(&peer_info.node_id.key, &mut nid_builder)?;
|
||||
let mut nids_builder = builder.reborrow().init_node_ids(
|
||||
peer_info
|
||||
.node_ids
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
);
|
||||
for (i, nid) in peer_info.node_ids.iter().enumerate() {
|
||||
encode_typed_key(
|
||||
nid,
|
||||
&mut nids_builder.reborrow().get(
|
||||
i.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
),
|
||||
);
|
||||
}
|
||||
let mut sni_builder = builder.reborrow().init_signed_node_info();
|
||||
encode_signed_node_info(&peer_info.signed_node_info, &mut sni_builder)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode_peer_info(reader: &veilid_capnp::peer_info::Reader) -> Result<PeerInfo, RPCError> {
|
||||
let nid_reader = reader
|
||||
pub fn decode_peer_info(
|
||||
reader: &veilid_capnp::peer_info::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<PeerInfo, RPCError> {
|
||||
let nids_reader = reader
|
||||
.reborrow()
|
||||
.get_node_id()
|
||||
.get_node_ids()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let sni_reader = reader
|
||||
.reborrow()
|
||||
.get_signed_node_info()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let node_id = NodeId::new(decode_dht_key(&nid_reader));
|
||||
let signed_node_info = decode_signed_node_info(&sni_reader, &node_id.key)?;
|
||||
|
||||
let mut node_ids = TypedKeySet::with_capacity(nids_reader.len() as usize);
|
||||
for nid_reader in nids_reader.iter() {
|
||||
node_ids.add(decode_typed_key(&nid_reader)?);
|
||||
}
|
||||
let signed_node_info = decode_signed_node_info(&sni_reader, crypto, &mut node_ids)?;
|
||||
if node_ids.len() == 0 {
|
||||
return Err(RPCError::protocol("no verified node ids"));
|
||||
}
|
||||
Ok(PeerInfo {
|
||||
node_id,
|
||||
node_ids,
|
||||
signed_node_info,
|
||||
})
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ pub fn encode_route_hop(
|
||||
match &route_hop.node {
|
||||
RouteNode::NodeId(ni) => {
|
||||
let mut ni_builder = node_builder.init_node_id();
|
||||
encode_dht_key(&ni.key, &mut ni_builder)?;
|
||||
encode_key256(&ni, &mut ni_builder);
|
||||
}
|
||||
RouteNode::PeerInfo(pi) => {
|
||||
let mut pi_builder = node_builder.init_peer_info();
|
||||
@ -67,17 +67,20 @@ pub fn encode_route_hop(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode_route_hop(reader: &veilid_capnp::route_hop::Reader) -> Result<RouteHop, RPCError> {
|
||||
pub fn decode_route_hop(
|
||||
reader: &veilid_capnp::route_hop::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<RouteHop, RPCError> {
|
||||
let n_reader = reader.reborrow().get_node();
|
||||
let node = match n_reader.which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::route_hop::node::Which::NodeId(ni) => {
|
||||
let ni_reader = ni.map_err(RPCError::protocol)?;
|
||||
RouteNode::NodeId(NodeId::new(decode_dht_key(&ni_reader)))
|
||||
RouteNode::NodeId(decode_key256(&ni_reader))
|
||||
}
|
||||
veilid_capnp::route_hop::node::Which::PeerInfo(pi) => {
|
||||
let pi_reader = pi.map_err(RPCError::protocol)?;
|
||||
RouteNode::PeerInfo(
|
||||
decode_peer_info(&pi_reader)
|
||||
decode_peer_info(&pi_reader, crypto)
|
||||
.map_err(RPCError::map_protocol("invalid peer info in route hop"))?,
|
||||
)
|
||||
}
|
||||
@ -101,10 +104,10 @@ pub fn encode_private_route(
|
||||
private_route: &PrivateRoute,
|
||||
builder: &mut veilid_capnp::private_route::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
encode_dht_key(
|
||||
encode_typed_key(
|
||||
&private_route.public_key,
|
||||
&mut builder.reborrow().init_public_key(),
|
||||
)?;
|
||||
);
|
||||
builder.set_hop_count(private_route.hop_count);
|
||||
let mut h_builder = builder.reborrow().init_hops();
|
||||
match &private_route.hops {
|
||||
@ -125,16 +128,17 @@ pub fn encode_private_route(
|
||||
|
||||
pub fn decode_private_route(
|
||||
reader: &veilid_capnp::private_route::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<PrivateRoute, RPCError> {
|
||||
let public_key = decode_dht_key(&reader.get_public_key().map_err(RPCError::map_protocol(
|
||||
"invalid public key in private route",
|
||||
))?);
|
||||
let public_key = decode_typed_key(&reader.get_public_key().map_err(
|
||||
RPCError::map_protocol("invalid public key in private route"),
|
||||
)?)?;
|
||||
let hop_count = reader.get_hop_count();
|
||||
|
||||
let hops = match reader.get_hops().which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::private_route::hops::Which::FirstHop(rh_reader) => {
|
||||
let rh_reader = rh_reader.map_err(RPCError::protocol)?;
|
||||
PrivateRouteHops::FirstHop(decode_route_hop(&rh_reader)?)
|
||||
PrivateRouteHops::FirstHop(decode_route_hop(&rh_reader, crypto)?)
|
||||
}
|
||||
veilid_capnp::private_route::hops::Which::Data(rhd_reader) => {
|
||||
let rhd_reader = rhd_reader.map_err(RPCError::protocol)?;
|
||||
@ -156,10 +160,10 @@ pub fn encode_safety_route(
|
||||
safety_route: &SafetyRoute,
|
||||
builder: &mut veilid_capnp::safety_route::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
encode_dht_key(
|
||||
encode_typed_key(
|
||||
&safety_route.public_key,
|
||||
&mut builder.reborrow().init_public_key(),
|
||||
)?;
|
||||
);
|
||||
builder.set_hop_count(safety_route.hop_count);
|
||||
let h_builder = builder.reborrow().init_hops();
|
||||
match &safety_route.hops {
|
||||
@ -178,12 +182,13 @@ pub fn encode_safety_route(
|
||||
|
||||
pub fn decode_safety_route(
|
||||
reader: &veilid_capnp::safety_route::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<SafetyRoute, RPCError> {
|
||||
let public_key = decode_dht_key(
|
||||
let public_key = decode_typed_key(
|
||||
&reader
|
||||
.get_public_key()
|
||||
.map_err(RPCError::map_protocol("invalid public key in safety route"))?,
|
||||
);
|
||||
)?;
|
||||
let hop_count = reader.get_hop_count();
|
||||
let hops = match reader.get_hops().which().map_err(RPCError::protocol)? {
|
||||
veilid_capnp::safety_route::hops::Which::Data(rhd_reader) => {
|
||||
@ -192,7 +197,7 @@ pub fn decode_safety_route(
|
||||
}
|
||||
veilid_capnp::safety_route::hops::Which::Private(pr_reader) => {
|
||||
let pr_reader = pr_reader.map_err(RPCError::protocol)?;
|
||||
SafetyRouteHops::Private(decode_private_route(&pr_reader)?)
|
||||
SafetyRouteHops::Private(decode_private_route(&pr_reader, crypto)?)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -34,6 +34,7 @@ pub fn encode_signal_info(
|
||||
|
||||
pub fn decode_signal_info(
|
||||
reader: &veilid_capnp::operation_signal::Reader,
|
||||
crypto: Crypto,
|
||||
) -> Result<SignalInfo, RPCError> {
|
||||
Ok(
|
||||
match reader
|
||||
@ -52,7 +53,7 @@ pub fn decode_signal_info(
|
||||
let pi_reader = r.get_peer_info().map_err(RPCError::map_protocol(
|
||||
"invalid peer info in hole punch signal info",
|
||||
))?;
|
||||
let peer_info = decode_peer_info(&pi_reader)?;
|
||||
let peer_info = decode_peer_info(&pi_reader, crypto)?;
|
||||
|
||||
SignalInfo::HolePunch { receipt, peer_info }
|
||||
}
|
||||
@ -68,7 +69,7 @@ pub fn decode_signal_info(
|
||||
let pi_reader = r.get_peer_info().map_err(RPCError::map_protocol(
|
||||
"invalid peer info in reverse connect signal info",
|
||||
))?;
|
||||
let peer_info = decode_peer_info(&pi_reader)?;
|
||||
let peer_info = decode_peer_info(&pi_reader, crypto)?;
|
||||
|
||||
SignalInfo::ReverseConnect { receipt, peer_info }
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
pub fn encode_signature(sig: &DHTSignature, builder: &mut veilid_capnp::signature512::Builder) {
|
||||
pub fn encode_signature512(sig: &Signature, builder: &mut veilid_capnp::signature512::Builder) {
|
||||
let sig = &sig.bytes;
|
||||
|
||||
builder.set_u0(u64::from_be_bytes(
|
||||
@ -29,7 +29,7 @@ pub fn encode_signature(sig: &DHTSignature, builder: &mut veilid_capnp::signatur
|
||||
));
|
||||
}
|
||||
|
||||
pub fn decode_signature(reader: &veilid_capnp::signature512::Reader) -> DHTSignature {
|
||||
pub fn decode_signature512(reader: &veilid_capnp::signature512::Reader) -> Signature {
|
||||
let u0 = reader.get_u0().to_be_bytes();
|
||||
let u1 = reader.get_u1().to_be_bytes();
|
||||
let u2 = reader.get_u2().to_be_bytes();
|
||||
@ -39,7 +39,7 @@ pub fn decode_signature(reader: &veilid_capnp::signature512::Reader) -> DHTSigna
|
||||
let u6 = reader.get_u6().to_be_bytes();
|
||||
let u7 = reader.get_u7().to_be_bytes();
|
||||
|
||||
DHTSignature::new([
|
||||
Signature::new([
|
||||
u0[0], u0[1], u0[2], u0[3], u0[4], u0[5], u0[6], u0[7], // u0
|
||||
u1[0], u1[1], u1[2], u1[3], u1[4], u1[5], u1[6], u1[7], // u1
|
||||
u2[0], u2[1], u2[2], u2[3], u2[4], u2[5], u2[6], u2[7], // u2
|
@ -12,18 +12,30 @@ pub fn encode_signed_direct_node_info(
|
||||
.reborrow()
|
||||
.set_timestamp(signed_direct_node_info.timestamp.into());
|
||||
|
||||
let mut sig_builder = builder.reborrow().init_signature();
|
||||
let Some(signature) = &signed_direct_node_info.signature else {
|
||||
return Err(RPCError::internal("Should not encode SignedDirectNodeInfo without signature!"));
|
||||
};
|
||||
encode_signature(signature, &mut sig_builder);
|
||||
let mut sigs_builder = builder.reborrow().init_signatures(
|
||||
signed_direct_node_info
|
||||
.signatures
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
);
|
||||
for (i, typed_signature) in signed_direct_node_info.signatures.iter().enumerate() {
|
||||
encode_typed_signature(
|
||||
typed_signature,
|
||||
&mut sigs_builder.reborrow().get(
|
||||
i.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode_signed_direct_node_info(
|
||||
reader: &veilid_capnp::signed_direct_node_info::Reader,
|
||||
node_id: &DHTKey,
|
||||
crypto: Crypto,
|
||||
node_ids: &mut TypedKeySet,
|
||||
) -> Result<SignedDirectNodeInfo, RPCError> {
|
||||
let ni_reader = reader
|
||||
.reborrow()
|
||||
@ -31,15 +43,24 @@ pub fn decode_signed_direct_node_info(
|
||||
.map_err(RPCError::protocol)?;
|
||||
let node_info = decode_node_info(&ni_reader)?;
|
||||
|
||||
let sig_reader = reader
|
||||
.reborrow()
|
||||
.get_signature()
|
||||
.map_err(RPCError::protocol)?;
|
||||
|
||||
let timestamp = reader.reborrow().get_timestamp().into();
|
||||
|
||||
let signature = decode_signature(&sig_reader);
|
||||
let sigs_reader = reader
|
||||
.reborrow()
|
||||
.get_signatures()
|
||||
.map_err(RPCError::protocol)?;
|
||||
|
||||
SignedDirectNodeInfo::new(NodeId::new(*node_id), node_info, timestamp, signature)
|
||||
let sig_count = sigs_reader.len() as usize;
|
||||
if sig_count > MAX_CRYPTO_KINDS {
|
||||
return Err(RPCError::protocol("too many signatures"));
|
||||
}
|
||||
|
||||
let mut typed_signatures = Vec::with_capacity(sig_count);
|
||||
for sig_reader in sigs_reader {
|
||||
let typed_signature = decode_typed_signature(&sig_reader)?;
|
||||
typed_signatures.push(typed_signature);
|
||||
}
|
||||
|
||||
SignedDirectNodeInfo::new(crypto, node_ids, node_info, timestamp, typed_signatures)
|
||||
.map_err(RPCError::protocol)
|
||||
}
|
||||
|
@ -20,20 +20,21 @@ pub fn encode_signed_node_info(
|
||||
|
||||
pub fn decode_signed_node_info(
|
||||
reader: &veilid_capnp::signed_node_info::Reader,
|
||||
node_id: &DHTKey,
|
||||
crypto: Crypto,
|
||||
node_ids: &mut TypedKeySet,
|
||||
) -> Result<SignedNodeInfo, RPCError> {
|
||||
match reader
|
||||
.which()
|
||||
.map_err(RPCError::map_internal("invalid signal operation"))?
|
||||
.map_err(RPCError::map_internal("invalid signed node info"))?
|
||||
{
|
||||
veilid_capnp::signed_node_info::Direct(d) => {
|
||||
let d_reader = d.map_err(RPCError::protocol)?;
|
||||
let sdni = decode_signed_direct_node_info(&d_reader, node_id)?;
|
||||
let sdni = decode_signed_direct_node_info(&d_reader, crypto, node_ids)?;
|
||||
Ok(SignedNodeInfo::Direct(sdni))
|
||||
}
|
||||
veilid_capnp::signed_node_info::Relayed(r) => {
|
||||
let r_reader = r.map_err(RPCError::protocol)?;
|
||||
let srni = decode_signed_relayed_node_info(&r_reader, node_id)?;
|
||||
let srni = decode_signed_relayed_node_info(&r_reader, crypto, node_ids)?;
|
||||
Ok(SignedNodeInfo::Relayed(srni))
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,22 @@ pub fn encode_signed_relayed_node_info(
|
||||
let mut ni_builder = builder.reborrow().init_node_info();
|
||||
encode_node_info(&signed_relayed_node_info.node_info, &mut ni_builder)?;
|
||||
|
||||
let mut rid_builder = builder.reborrow().init_relay_id();
|
||||
encode_dht_key(&signed_relayed_node_info.relay_id.key, &mut rid_builder)?;
|
||||
let mut rids_builder = builder.reborrow().init_relay_ids(
|
||||
signed_relayed_node_info
|
||||
.relay_ids
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
);
|
||||
for (i, typed_key) in signed_relayed_node_info.relay_ids.iter().enumerate() {
|
||||
encode_typed_key(
|
||||
typed_key,
|
||||
&mut rids_builder.reborrow().get(
|
||||
i.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let mut ri_builder = builder.reborrow().init_relay_info();
|
||||
encode_signed_direct_node_info(&signed_relayed_node_info.relay_info, &mut ri_builder)?;
|
||||
@ -18,15 +32,30 @@ pub fn encode_signed_relayed_node_info(
|
||||
.reborrow()
|
||||
.set_timestamp(signed_relayed_node_info.timestamp.into());
|
||||
|
||||
let mut sig_builder = builder.reborrow().init_signature();
|
||||
encode_signature(&signed_relayed_node_info.signature, &mut sig_builder);
|
||||
let mut sigs_builder = builder.reborrow().init_signatures(
|
||||
signed_relayed_node_info
|
||||
.signatures
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
);
|
||||
for (i, typed_signature) in signed_relayed_node_info.signatures.iter().enumerate() {
|
||||
encode_typed_signature(
|
||||
typed_signature,
|
||||
&mut sigs_builder.reborrow().get(
|
||||
i.try_into()
|
||||
.map_err(RPCError::map_invalid_format("out of bound error"))?,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode_signed_relayed_node_info(
|
||||
reader: &veilid_capnp::signed_relayed_node_info::Reader,
|
||||
node_id: &DHTKey,
|
||||
crypto: Crypto,
|
||||
node_ids: &mut TypedKeySet,
|
||||
) -> Result<SignedRelayedNodeInfo, RPCError> {
|
||||
let ni_reader = reader
|
||||
.reborrow()
|
||||
@ -34,33 +63,64 @@ pub fn decode_signed_relayed_node_info(
|
||||
.map_err(RPCError::protocol)?;
|
||||
let node_info = decode_node_info(&ni_reader)?;
|
||||
|
||||
let rid_reader = reader
|
||||
let rids_reader = reader
|
||||
.reborrow()
|
||||
.get_relay_id()
|
||||
.get_relay_ids()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let relay_id = decode_dht_key(&rid_reader);
|
||||
let rid_count = rids_reader.len() as usize;
|
||||
if rid_count > MAX_CRYPTO_KINDS {
|
||||
return Err(RPCError::protocol("too many relay ids"));
|
||||
}
|
||||
let mut relay_ids = TypedKeySet::with_capacity(rid_count);
|
||||
for rid_reader in rids_reader {
|
||||
let relay_id = decode_typed_key(&rid_reader)?;
|
||||
relay_ids.add(relay_id);
|
||||
}
|
||||
|
||||
let ri_reader = reader
|
||||
.reborrow()
|
||||
.get_relay_info()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let relay_info = decode_signed_direct_node_info(&ri_reader, &relay_id)?;
|
||||
let relay_info = decode_signed_direct_node_info(&ri_reader, crypto.clone(), &mut relay_ids)?;
|
||||
|
||||
// Ensure the relay info for the node has a superset of the crypto kinds of the node it is relaying
|
||||
if common_crypto_kinds(
|
||||
&node_info.crypto_support,
|
||||
&relay_info.node_info.crypto_support,
|
||||
)
|
||||
.len()
|
||||
!= node_info.crypto_support.len()
|
||||
{
|
||||
return Err(RPCError::protocol(
|
||||
"relay should have superset of node crypto kinds",
|
||||
));
|
||||
}
|
||||
|
||||
let sig_reader = reader
|
||||
.reborrow()
|
||||
.get_signature()
|
||||
.map_err(RPCError::protocol)?;
|
||||
let timestamp = reader.reborrow().get_timestamp().into();
|
||||
|
||||
let signature = decode_signature(&sig_reader);
|
||||
let sigs_reader = reader
|
||||
.reborrow()
|
||||
.get_signatures()
|
||||
.map_err(RPCError::protocol)?;
|
||||
|
||||
let sig_count = sigs_reader.len() as usize;
|
||||
if sig_count > MAX_CRYPTO_KINDS {
|
||||
return Err(RPCError::protocol("too many signatures"));
|
||||
}
|
||||
|
||||
let mut typed_signatures = Vec::with_capacity(sig_count);
|
||||
for sig_reader in sigs_reader {
|
||||
let typed_signature = decode_typed_signature(&sig_reader)?;
|
||||
typed_signatures.push(typed_signature);
|
||||
}
|
||||
SignedRelayedNodeInfo::new(
|
||||
NodeId::new(*node_id),
|
||||
crypto,
|
||||
node_ids,
|
||||
node_info,
|
||||
NodeId::new(relay_id),
|
||||
relay_ids,
|
||||
relay_info,
|
||||
timestamp,
|
||||
signature,
|
||||
typed_signatures,
|
||||
)
|
||||
.map_err(RPCError::protocol)
|
||||
}
|
||||
|
19
veilid-core/src/rpc_processor/coders/typed_key.rs
Normal file
19
veilid-core/src/rpc_processor/coders/typed_key.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use super::*;
|
||||
|
||||
pub fn decode_typed_key(typed_key: &veilid_capnp::typed_key::Reader) -> Result<TypedKey, RPCError> {
|
||||
let key_reader = typed_key
|
||||
.get_key()
|
||||
.map_err(RPCError::map_invalid_format("invalid typed key"))?;
|
||||
let kind = typed_key.get_kind();
|
||||
|
||||
Ok(TypedKey::new(
|
||||
CryptoKind::from(kind.to_be_bytes()),
|
||||
decode_key256(&key_reader),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn encode_typed_key(typed_key: &TypedKey, builder: &mut veilid_capnp::typed_key::Builder) {
|
||||
builder.set_kind(u32::from_be_bytes(typed_key.kind.0));
|
||||
let mut key_builder = builder.reborrow().init_key();
|
||||
encode_key256(&typed_key.value, &mut key_builder);
|
||||
}
|
24
veilid-core/src/rpc_processor/coders/typed_signature.rs
Normal file
24
veilid-core/src/rpc_processor/coders/typed_signature.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use super::*;
|
||||
|
||||
pub fn decode_typed_signature(
|
||||
typed_signature: &veilid_capnp::typed_signature::Reader,
|
||||
) -> Result<TypedSignature, RPCError> {
|
||||
let sig_reader = typed_signature
|
||||
.get_signature()
|
||||
.map_err(RPCError::map_invalid_format("invalid typed signature"))?;
|
||||
let kind = typed_signature.get_kind();
|
||||
|
||||
Ok(TypedSignature::new(
|
||||
CryptoKind::from(kind.to_be_bytes()),
|
||||
decode_signature512(&sig_reader),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn encode_typed_signature(
|
||||
typed_signature: &TypedSignature,
|
||||
builder: &mut veilid_capnp::typed_signature::Builder,
|
||||
) {
|
||||
builder.set_kind(u32::from_be_bytes(typed_signature.kind.0));
|
||||
let mut sig_builder = builder.reborrow().init_signature();
|
||||
encode_signature512(&typed_signature.value, &mut sig_builder);
|
||||
}
|
@ -5,6 +5,7 @@ pub fn encode_value_data(
|
||||
builder: &mut veilid_capnp::value_data::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
builder.set_data(&value_data.data);
|
||||
builder.set_schema(u32::from_be_bytes(value_data.schema.0));
|
||||
builder.set_seq(value_data.seq);
|
||||
Ok(())
|
||||
}
|
||||
@ -12,5 +13,6 @@ pub fn encode_value_data(
|
||||
pub fn decode_value_data(reader: &veilid_capnp::value_data::Reader) -> Result<ValueData, RPCError> {
|
||||
let data = reader.get_data().map_err(RPCError::protocol)?.to_vec();
|
||||
let seq = reader.get_seq();
|
||||
Ok(ValueData { data, seq })
|
||||
let schema = FourCC::from(reader.get_schema().to_be_bytes());
|
||||
Ok(ValueData { data, schema, seq })
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
pub fn encode_value_key(
|
||||
value_key: &ValueKey,
|
||||
builder: &mut veilid_capnp::value_key::Builder,
|
||||
) -> Result<(), RPCError> {
|
||||
let mut pk_builder = builder.reborrow().init_public_key();
|
||||
encode_dht_key(&value_key.key, &mut pk_builder)?;
|
||||
if let Some(subkey) = &value_key.subkey {
|
||||
builder.set_subkey(subkey);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode_value_key(reader: &veilid_capnp::value_key::Reader) -> Result<ValueKey, RPCError> {
|
||||
let pk_reader = reader.get_public_key().map_err(RPCError::protocol)?;
|
||||
let key = decode_dht_key(&pk_reader);
|
||||
let subkey = if !reader.has_subkey() {
|
||||
None
|
||||
} else {
|
||||
let subkey = reader.get_subkey().map_err(RPCError::protocol)?;
|
||||
Some(subkey.to_owned())
|
||||
};
|
||||
Ok(ValueKey { key, subkey })
|
||||
}
|
@ -15,11 +15,11 @@ pub enum Destination {
|
||||
/// The relay to send to
|
||||
relay: NodeRef,
|
||||
/// The final destination the relay should send to
|
||||
target: DHTKey,
|
||||
target: NodeRef,
|
||||
/// Require safety route or not
|
||||
safety_selection: SafetySelection,
|
||||
},
|
||||
/// Send to private route (privateroute)
|
||||
/// Send to private route
|
||||
PrivateRoute {
|
||||
/// A private route to send to
|
||||
private_route: PrivateRoute,
|
||||
@ -36,7 +36,7 @@ impl Destination {
|
||||
safety_selection: SafetySelection::Unsafe(sequencing),
|
||||
}
|
||||
}
|
||||
pub fn relay(relay: NodeRef, target: DHTKey) -> Self {
|
||||
pub fn relay(relay: NodeRef, target: NodeRef) -> Self {
|
||||
let sequencing = relay.sequencing();
|
||||
Self::Relay {
|
||||
relay,
|
||||
@ -124,7 +124,7 @@ impl fmt::Display for Destination {
|
||||
""
|
||||
};
|
||||
|
||||
write!(f, "{}@{}{}", target.encode(), relay, sr)
|
||||
write!(f, "{}@{}{}", target, relay, sr)
|
||||
}
|
||||
Destination::PrivateRoute {
|
||||
private_route,
|
||||
@ -136,7 +136,7 @@ impl fmt::Display for Destination {
|
||||
""
|
||||
};
|
||||
|
||||
write!(f, "{}{}", private_route, sr)
|
||||
write!(f, "{}{}", private_route.public_key, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,8 +162,9 @@ impl RPCProcessor {
|
||||
}
|
||||
SafetySelection::Safe(safety_spec) => {
|
||||
// Sent directly but with a safety route, respond to private route
|
||||
let crypto_kind = target.best_node_id().kind;
|
||||
let Some(pr_key) = rss
|
||||
.get_private_route_for_safety_spec(safety_spec, &[target.node_id()])
|
||||
.get_private_route_for_safety_spec(crypto_kind, safety_spec, &target.node_ids())
|
||||
.map_err(RPCError::internal)? else {
|
||||
return Ok(NetworkResult::no_connection_other("no private route for response at this time"));
|
||||
};
|
||||
@ -187,11 +188,15 @@ impl RPCProcessor {
|
||||
}
|
||||
SafetySelection::Safe(safety_spec) => {
|
||||
// Sent via a relay but with a safety route, respond to private route
|
||||
let crypto_kind = target.best_node_id().kind;
|
||||
|
||||
let mut avoid_nodes = relay.node_ids();
|
||||
avoid_nodes.add_all(&target.node_ids());
|
||||
let Some(pr_key) = rss
|
||||
.get_private_route_for_safety_spec(safety_spec, &[relay.node_id(), *target])
|
||||
.map_err(RPCError::internal)? else {
|
||||
return Ok(NetworkResult::no_connection_other("no private route for response at this time"));
|
||||
};
|
||||
.get_private_route_for_safety_spec(crypto_kind, safety_spec, &avoid_nodes)
|
||||
.map_err(RPCError::internal)? else {
|
||||
return Ok(NetworkResult::no_connection_other("no private route for response at this time"));
|
||||
};
|
||||
|
||||
// Get the assembled route for response
|
||||
let private_route = rss
|
||||
@ -205,50 +210,50 @@ impl RPCProcessor {
|
||||
private_route,
|
||||
safety_selection,
|
||||
} => {
|
||||
|
||||
let Some(avoid_node_id) = private_route.first_hop_node_id() else {
|
||||
return Err(RPCError::internal("destination private route must have first hop"));
|
||||
};
|
||||
|
||||
let crypto_kind = private_route.public_key.kind;
|
||||
|
||||
match safety_selection {
|
||||
SafetySelection::Unsafe(_) => {
|
||||
// Sent to a private route with no safety route, use a stub safety route for the response
|
||||
|
||||
// Determine if we can use optimized nodeinfo
|
||||
let route_node = match rss
|
||||
.has_remote_private_route_seen_our_node_info(&private_route.public_key)
|
||||
let route_node = if rss
|
||||
.has_remote_private_route_seen_our_node_info(&private_route.public_key.value)
|
||||
{
|
||||
true => {
|
||||
if !routing_table.has_valid_own_node_info(RoutingDomain::PublicInternet) {
|
||||
return Ok(NetworkResult::no_connection_other("Own node info must be valid to use private route"));
|
||||
}
|
||||
RouteNode::NodeId(NodeId::new(routing_table.node_id()))
|
||||
}
|
||||
false => {
|
||||
RouteNode::NodeId(routing_table.node_id(crypto_kind).value)
|
||||
} else {
|
||||
let Some(own_peer_info) =
|
||||
routing_table.get_own_peer_info(RoutingDomain::PublicInternet) else {
|
||||
return Ok(NetworkResult::no_connection_other("Own peer info must be valid to use private route"));
|
||||
};
|
||||
RouteNode::PeerInfo(own_peer_info)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(NetworkResult::value(RespondTo::PrivateRoute(
|
||||
PrivateRoute::new_stub(routing_table.node_id(), route_node),
|
||||
PrivateRoute::new_stub(routing_table.node_id(crypto_kind), route_node),
|
||||
)))
|
||||
}
|
||||
SafetySelection::Safe(safety_spec) => {
|
||||
// Sent to a private route via a safety route, respond to private route
|
||||
|
||||
|
||||
// Check for loopback test
|
||||
let pr_key = if safety_spec.preferred_route
|
||||
== Some(private_route.public_key)
|
||||
let opt_private_route_id = rss.get_route_id_for_key(&private_route.public_key.value);
|
||||
let pr_key = if opt_private_route_id.is_some() && safety_spec.preferred_route == opt_private_route_id
|
||||
{
|
||||
// Private route is also safety route during loopback test
|
||||
private_route.public_key
|
||||
private_route.public_key.value
|
||||
} else {
|
||||
// Get the privat route to respond to that matches the safety route spec we sent the request with
|
||||
// Get the private route to respond to that matches the safety route spec we sent the request with
|
||||
let Some(pr_key) = rss
|
||||
.get_private_route_for_safety_spec(safety_spec, &[avoid_node_id])
|
||||
.get_private_route_for_safety_spec(crypto_kind, safety_spec, &[avoid_node_id])
|
||||
.map_err(RPCError::internal)? else {
|
||||
return Ok(NetworkResult::no_connection_other("no private route for response at this time"));
|
||||
};
|
||||
@ -296,17 +301,25 @@ impl RPCProcessor {
|
||||
};
|
||||
|
||||
// Reply directly to the request's source
|
||||
let sender_id = detail.envelope.get_sender_id();
|
||||
let sender_node_id = TypedKey::new(detail.envelope.get_crypto_kind(), detail.envelope.get_sender_id());
|
||||
|
||||
// This may be a different node's reference than the 'sender' in the case of a relay
|
||||
let peer_noderef = detail.peer_noderef.clone();
|
||||
|
||||
// If the sender_id is that of the peer, then this is a direct reply
|
||||
// else it is a relayed reply through the peer
|
||||
if peer_noderef.node_id() == sender_id {
|
||||
if peer_noderef.node_ids().contains(&sender_node_id) {
|
||||
NetworkResult::value(Destination::direct(peer_noderef))
|
||||
} else {
|
||||
NetworkResult::value(Destination::relay(peer_noderef, sender_id))
|
||||
// Look up the sender node, we should have added it via senderNodeInfo before getting here.
|
||||
if let Some(sender_noderef) = self.routing_table.lookup_node_ref(sender_node_id) {
|
||||
NetworkResult::value(Destination::relay(peer_noderef, sender_noderef))
|
||||
} else {
|
||||
return NetworkResult::invalid_message(
|
||||
"not responding to sender that has no node info",
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
RespondTo::PrivateRoute(pr) => {
|
||||
|
@ -52,8 +52,10 @@ struct RPCMessageHeaderDetailDirect {
|
||||
/// Header details for rpc messages received over only a safety route but not a private route
|
||||
#[derive(Debug, Clone)]
|
||||
struct RPCMessageHeaderDetailSafetyRouted {
|
||||
/// Direct header
|
||||
direct: RPCMessageHeaderDetailDirect,
|
||||
/// Remote safety route used
|
||||
remote_safety_route: DHTKey,
|
||||
remote_safety_route: PublicKey,
|
||||
/// The sequencing used for this route
|
||||
sequencing: Sequencing,
|
||||
}
|
||||
@ -61,10 +63,12 @@ struct RPCMessageHeaderDetailSafetyRouted {
|
||||
/// Header details for rpc messages received over a private route
|
||||
#[derive(Debug, Clone)]
|
||||
struct RPCMessageHeaderDetailPrivateRouted {
|
||||
/// Direct header
|
||||
direct: RPCMessageHeaderDetailDirect,
|
||||
/// Remote safety route used (or possibly node id the case of no safety route)
|
||||
remote_safety_route: DHTKey,
|
||||
remote_safety_route: PublicKey,
|
||||
/// The private route we received the rpc over
|
||||
private_route: DHTKey,
|
||||
private_route: PublicKey,
|
||||
// The safety spec for replying to this private routed rpc
|
||||
safety_spec: SafetySpec,
|
||||
}
|
||||
@ -87,7 +91,16 @@ struct RPCMessageHeader {
|
||||
detail: RPCMessageHeaderDetail,
|
||||
}
|
||||
|
||||
impl RPCMessageHeader {}
|
||||
impl RPCMessageHeader {
|
||||
/// The crypto kind used on the RPC
|
||||
pub fn crypto_kind(&self) -> CryptoKind {
|
||||
match &self.detail {
|
||||
RPCMessageHeaderDetail::Direct(d) => d.envelope.get_crypto_kind(),
|
||||
RPCMessageHeaderDetail::SafetyRouted(s) => s.direct.envelope.get_crypto_kind(),
|
||||
RPCMessageHeaderDetail::PrivateRouted(p) => p.direct.envelope.get_crypto_kind(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RPCMessageData {
|
||||
@ -141,9 +154,9 @@ struct WaitableReply {
|
||||
node_ref: NodeRef,
|
||||
send_ts: Timestamp,
|
||||
send_data_kind: SendDataKind,
|
||||
safety_route: Option<DHTKey>,
|
||||
remote_private_route: Option<DHTKey>,
|
||||
reply_private_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
reply_private_route: Option<PublicKey>,
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@ -163,38 +176,38 @@ impl<T> Answer<T> {
|
||||
struct RenderedOperation {
|
||||
/// The rendered operation bytes
|
||||
message: Vec<u8>,
|
||||
/// Destination node id we're sending to
|
||||
node_id: DHTKey,
|
||||
/// Node to send envelope to (may not be destination node id in case of relay)
|
||||
/// Destination node we're sending to
|
||||
destination_node_ref: NodeRef,
|
||||
/// Node to send envelope to (may not be destination node in case of relay)
|
||||
node_ref: NodeRef,
|
||||
/// Total safety + private route hop count + 1 hop for the initial send
|
||||
hop_count: usize,
|
||||
/// The safety route used to send the message
|
||||
safety_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
/// The private route used to send the message
|
||||
remote_private_route: Option<DHTKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
/// The private route requested to receive the reply
|
||||
reply_private_route: Option<DHTKey>,
|
||||
reply_private_route: Option<PublicKey>,
|
||||
}
|
||||
|
||||
/// Node information exchanged during every RPC message
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SenderSignedNodeInfo {
|
||||
/// The current signed node info of the sender if required
|
||||
signed_node_info: Option<SignedNodeInfo>,
|
||||
pub struct SenderPeerInfo {
|
||||
/// The current peer info of the sender if required
|
||||
opt_sender_peer_info: Option<PeerInfo>,
|
||||
/// The last timestamp of the target's node info to assist remote node with sending its latest node info
|
||||
target_node_info_ts: Timestamp,
|
||||
}
|
||||
impl SenderSignedNodeInfo {
|
||||
pub fn new_no_sni(target_node_info_ts: Timestamp) -> Self {
|
||||
impl SenderPeerInfo {
|
||||
pub fn new_no_peer_info(target_node_info_ts: Timestamp) -> Self {
|
||||
Self {
|
||||
signed_node_info: None,
|
||||
opt_sender_peer_info: None,
|
||||
target_node_info_ts,
|
||||
}
|
||||
}
|
||||
pub fn new(sender_signed_node_info: SignedNodeInfo, target_node_info_ts: Timestamp) -> Self {
|
||||
pub fn new(sender_peer_info: PeerInfo, target_node_info_ts: Timestamp) -> Self {
|
||||
Self {
|
||||
signed_node_info: Some(sender_signed_node_info),
|
||||
opt_sender_peer_info: Some(sender_peer_info),
|
||||
target_node_info_ts,
|
||||
}
|
||||
}
|
||||
@ -371,7 +384,7 @@ impl RPCProcessor {
|
||||
/// If no node was found in the timeout, this returns None
|
||||
pub async fn search_dht_single_key(
|
||||
&self,
|
||||
_node_id: DHTKey,
|
||||
_node_id: PublicKey,
|
||||
_count: u32,
|
||||
_fanout: u32,
|
||||
_timeout: Option<u64>,
|
||||
@ -386,7 +399,7 @@ impl RPCProcessor {
|
||||
/// Search the DHT for the 'count' closest nodes to a key, adding them all to the routing table if they are not there and returning their node references
|
||||
pub async fn search_dht_multi_key(
|
||||
&self,
|
||||
_node_id: DHTKey,
|
||||
_node_id: PublicKey,
|
||||
_count: u32,
|
||||
_fanout: u32,
|
||||
_timeout: Option<u64>,
|
||||
@ -399,14 +412,14 @@ impl RPCProcessor {
|
||||
/// Note: This routine can possible be recursive, hence the SendPinBoxFuture async form
|
||||
pub fn resolve_node(
|
||||
&self,
|
||||
node_id: DHTKey,
|
||||
node_id: PublicKey,
|
||||
) -> SendPinBoxFuture<Result<Option<NodeRef>, RPCError>> {
|
||||
let this = self.clone();
|
||||
Box::pin(async move {
|
||||
let routing_table = this.routing_table();
|
||||
|
||||
// First see if we have the node in our routing table already
|
||||
if let Some(nr) = routing_table.lookup_node_ref(node_id) {
|
||||
if let Some(nr) = routing_table.lookup_any_node_ref(node_id) {
|
||||
// ensure we have some dial info for the entry already,
|
||||
// if not, we should do the find_node anyway
|
||||
if nr.has_any_dial_info() {
|
||||
@ -429,7 +442,7 @@ impl RPCProcessor {
|
||||
.await?;
|
||||
|
||||
if let Some(nr) = &nr {
|
||||
if nr.node_id() != node_id {
|
||||
if nr.node_ids().contains_key(&node_id) {
|
||||
// found a close node, but not exact within our configured resolve_node timeout
|
||||
return Ok(None);
|
||||
}
|
||||
@ -483,14 +496,20 @@ impl RPCProcessor {
|
||||
&self,
|
||||
safety_selection: SafetySelection,
|
||||
remote_private_route: PrivateRoute,
|
||||
reply_private_route: Option<DHTKey>,
|
||||
reply_private_route: Option<PublicKey>,
|
||||
message_data: Vec<u8>,
|
||||
) -> Result<NetworkResult<RenderedOperation>, RPCError> {
|
||||
let routing_table = self.routing_table();
|
||||
let rss = routing_table.route_spec_store();
|
||||
|
||||
// Get useful private route properties
|
||||
let pr_is_stub = remote_private_route.is_stub();
|
||||
let pr_hop_count = remote_private_route.hop_count;
|
||||
let pr_pubkey = remote_private_route.public_key;
|
||||
let pr_pubkey = remote_private_route.public_key.value;
|
||||
let crypto_kind = remote_private_route.crypto_kind();
|
||||
let Some(vcrypto) = self.crypto.get(crypto_kind) else {
|
||||
return Err(RPCError::internal("crypto not available for selected private route"));
|
||||
};
|
||||
|
||||
// Compile the safety route with the private route
|
||||
let compiled_route: CompiledRoute = match rss
|
||||
@ -505,27 +524,21 @@ impl RPCProcessor {
|
||||
}
|
||||
};
|
||||
let sr_is_stub = compiled_route.safety_route.is_stub();
|
||||
let sr_pubkey = compiled_route.safety_route.public_key;
|
||||
let sr_pubkey = compiled_route.safety_route.public_key.value;
|
||||
|
||||
// Encrypt routed operation
|
||||
// Xmsg + ENC(Xmsg, DH(PKapr, SKbsr))
|
||||
// xxx use factory method, get version from somewhere...
|
||||
let nonce = Crypto::get_random_nonce();
|
||||
let dh_secret = self
|
||||
.crypto
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let dh_secret = vcrypto
|
||||
.cached_dh(&pr_pubkey, &compiled_route.secret)
|
||||
.map_err(RPCError::map_internal("dh failed"))?;
|
||||
let enc_msg_data = Crypto::encrypt_aead(&message_data, &nonce, &dh_secret, None)
|
||||
let enc_msg_data = vcrypto
|
||||
.encrypt_aead(&message_data, &nonce, &dh_secret, None)
|
||||
.map_err(RPCError::map_internal("encryption failed"))?;
|
||||
|
||||
// Make the routed operation
|
||||
// xxx: replace MAX_CRYPTO_VERSION with the version from the factory
|
||||
let operation = RoutedOperation::new(
|
||||
MAX_CRYPTO_VERSION,
|
||||
safety_selection.get_sequencing(),
|
||||
nonce,
|
||||
enc_msg_data,
|
||||
);
|
||||
let operation =
|
||||
RoutedOperation::new(safety_selection.get_sequencing(), nonce, enc_msg_data);
|
||||
|
||||
// Prepare route operation
|
||||
let sr_hop_count = compiled_route.safety_route.hop_count;
|
||||
@ -533,8 +546,8 @@ impl RPCProcessor {
|
||||
safety_route: compiled_route.safety_route,
|
||||
operation,
|
||||
};
|
||||
let ssni_route = self
|
||||
.get_sender_signed_node_info(&Destination::direct(compiled_route.first_hop.clone()))?;
|
||||
let ssni_route =
|
||||
self.get_sender_peer_info(&Destination::direct(compiled_route.first_hop.clone()));
|
||||
let operation = RPCOperation::new_statement(
|
||||
RPCStatement::new(RPCStatementDetail::Route(route_operation)),
|
||||
ssni_route,
|
||||
@ -547,12 +560,11 @@ impl RPCProcessor {
|
||||
let out_message = builder_to_vec(route_msg)?;
|
||||
|
||||
// Get the first hop this is going to
|
||||
let out_node_id = compiled_route.first_hop.node_id();
|
||||
let out_hop_count = (1 + sr_hop_count + pr_hop_count) as usize;
|
||||
|
||||
let out = RenderedOperation {
|
||||
message: out_message,
|
||||
node_id: out_node_id,
|
||||
destination_node_ref: compiled_route.first_hop.clone(),
|
||||
node_ref: compiled_route.first_hop,
|
||||
hop_count: out_hop_count,
|
||||
safety_route: if sr_is_stub { None } else { Some(sr_pubkey) },
|
||||
@ -587,7 +599,7 @@ impl RPCProcessor {
|
||||
let reply_private_route = match operation.kind() {
|
||||
RPCOperationKind::Question(q) => match q.respond_to() {
|
||||
RespondTo::Sender => None,
|
||||
RespondTo::PrivateRoute(pr) => Some(pr.public_key),
|
||||
RespondTo::PrivateRoute(pr) => Some(pr.public_key.value),
|
||||
},
|
||||
RPCOperationKind::Statement(_) | RPCOperationKind::Answer(_) => None,
|
||||
};
|
||||
@ -607,16 +619,15 @@ impl RPCProcessor {
|
||||
// --------------------------------------
|
||||
|
||||
// Get the actual destination node id accounting for relays
|
||||
let (node_ref, node_id) = if let Destination::Relay {
|
||||
let (node_ref, destination_node_ref) = if let Destination::Relay {
|
||||
relay: _,
|
||||
target: ref dht_key,
|
||||
ref target,
|
||||
safety_selection: _,
|
||||
} = dest
|
||||
{
|
||||
(node_ref.clone(), dht_key.clone())
|
||||
(node_ref.clone(), target.clone())
|
||||
} else {
|
||||
let node_id = node_ref.node_id();
|
||||
(node_ref.clone(), node_id)
|
||||
(node_ref.clone(), node_ref.clone())
|
||||
};
|
||||
|
||||
// Handle the existence of safety route
|
||||
@ -635,7 +646,7 @@ impl RPCProcessor {
|
||||
// route, we can use a direct envelope instead of routing
|
||||
out = NetworkResult::value(RenderedOperation {
|
||||
message,
|
||||
node_id,
|
||||
destination_node_ref,
|
||||
node_ref,
|
||||
hop_count: 1,
|
||||
safety_route: None,
|
||||
@ -646,7 +657,9 @@ impl RPCProcessor {
|
||||
SafetySelection::Safe(_) => {
|
||||
// No private route was specified for the request
|
||||
// but we are using a safety route, so we must create an empty private route
|
||||
let peer_info = match node_ref.make_peer_info(RoutingDomain::PublicInternet)
|
||||
// Destination relay is ignored for safety routed operations
|
||||
let peer_info = match destination_node_ref
|
||||
.make_peer_info(RoutingDomain::PublicInternet)
|
||||
{
|
||||
None => {
|
||||
return Ok(NetworkResult::no_connection_other(
|
||||
@ -655,8 +668,10 @@ impl RPCProcessor {
|
||||
}
|
||||
Some(pi) => pi,
|
||||
};
|
||||
let private_route =
|
||||
PrivateRoute::new_stub(node_id, RouteNode::PeerInfo(peer_info));
|
||||
let private_route = PrivateRoute::new_stub(
|
||||
destination_node_ref.best_node_id(),
|
||||
RouteNode::PeerInfo(peer_info),
|
||||
);
|
||||
|
||||
// Wrap with safety route
|
||||
out = self.wrap_with_route(
|
||||
@ -689,18 +704,17 @@ impl RPCProcessor {
|
||||
|
||||
/// Get signed node info to package with RPC messages to improve
|
||||
/// routing table caching when it is okay to do so
|
||||
#[instrument(level = "trace", skip(self), ret, err)]
|
||||
fn get_sender_signed_node_info(
|
||||
&self,
|
||||
dest: &Destination,
|
||||
) -> Result<SenderSignedNodeInfo, RPCError> {
|
||||
/// Also check target's timestamp of our own node info, to see if we should send that
|
||||
/// And send our timestamp of the target's node info so they can determine if they should update us on their next rpc
|
||||
#[instrument(level = "trace", skip(self), ret)]
|
||||
fn get_sender_peer_info(&self, dest: &Destination) -> SenderPeerInfo {
|
||||
// Don't do this if the sender is to remain private
|
||||
// Otherwise we would be attaching the original sender's identity to the final destination,
|
||||
// thus defeating the purpose of the safety route entirely :P
|
||||
match dest.get_safety_selection() {
|
||||
SafetySelection::Unsafe(_) => {}
|
||||
SafetySelection::Safe(_) => {
|
||||
return Ok(SenderSignedNodeInfo::default());
|
||||
return SenderPeerInfo::default();
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,25 +729,19 @@ impl RPCProcessor {
|
||||
relay: _,
|
||||
target,
|
||||
safety_selection: _,
|
||||
} => {
|
||||
if let Some(target) = routing_table.lookup_node_ref(*target) {
|
||||
target
|
||||
} else {
|
||||
// Target was not in our routing table
|
||||
return Ok(SenderSignedNodeInfo::default());
|
||||
}
|
||||
}
|
||||
} => target.clone(),
|
||||
Destination::PrivateRoute {
|
||||
private_route: _,
|
||||
safety_selection: _,
|
||||
} => {
|
||||
return Ok(SenderSignedNodeInfo::default());
|
||||
return SenderPeerInfo::default();
|
||||
}
|
||||
};
|
||||
|
||||
let Some(routing_domain) = target.best_routing_domain() else {
|
||||
// No routing domain for target?
|
||||
return Err(RPCError::internal(format!("No routing domain for target: {}", target)));
|
||||
// No routing domain for target, no node info
|
||||
// Only a stale connection or no connection exists
|
||||
return SenderPeerInfo::default();
|
||||
};
|
||||
|
||||
// Get the target's node info timestamp
|
||||
@ -741,7 +749,7 @@ impl RPCProcessor {
|
||||
|
||||
// Don't return our node info if it's not valid yet
|
||||
let Some(own_peer_info) = routing_table.get_own_peer_info(routing_domain) else {
|
||||
return Ok(SenderSignedNodeInfo::new_no_sni(target_node_info_ts));
|
||||
return SenderPeerInfo::new_no_peer_info(target_node_info_ts);
|
||||
};
|
||||
|
||||
// Get our node info timestamp
|
||||
@ -749,13 +757,10 @@ impl RPCProcessor {
|
||||
|
||||
// If the target has seen our node info already don't send it again
|
||||
if target.has_seen_our_node_info_ts(routing_domain, our_node_info_ts) {
|
||||
return Ok(SenderSignedNodeInfo::new_no_sni(target_node_info_ts));
|
||||
return SenderPeerInfo::new_no_peer_info(target_node_info_ts);
|
||||
}
|
||||
|
||||
Ok(SenderSignedNodeInfo::new(
|
||||
own_peer_info.signed_node_info,
|
||||
target_node_info_ts,
|
||||
))
|
||||
SenderPeerInfo::new(own_peer_info, target_node_info_ts)
|
||||
}
|
||||
|
||||
/// Record failure to send to node or route
|
||||
@ -764,8 +769,8 @@ impl RPCProcessor {
|
||||
rpc_kind: RPCKind,
|
||||
send_ts: Timestamp,
|
||||
node_ref: NodeRef,
|
||||
safety_route: Option<DHTKey>,
|
||||
remote_private_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
) {
|
||||
let wants_answer = matches!(rpc_kind, RPCKind::Question);
|
||||
|
||||
@ -793,9 +798,9 @@ impl RPCProcessor {
|
||||
&self,
|
||||
send_ts: Timestamp,
|
||||
node_ref: NodeRef,
|
||||
safety_route: Option<DHTKey>,
|
||||
remote_private_route: Option<DHTKey>,
|
||||
private_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
private_route: Option<PublicKey>,
|
||||
) {
|
||||
// Record for node if this was not sent via a route
|
||||
if safety_route.is_none() && remote_private_route.is_none() {
|
||||
@ -833,8 +838,8 @@ impl RPCProcessor {
|
||||
send_ts: Timestamp,
|
||||
bytes: ByteCount,
|
||||
node_ref: NodeRef,
|
||||
safety_route: Option<DHTKey>,
|
||||
remote_private_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
) {
|
||||
let wants_answer = matches!(rpc_kind, RPCKind::Question);
|
||||
|
||||
@ -870,9 +875,9 @@ impl RPCProcessor {
|
||||
recv_ts: Timestamp,
|
||||
bytes: ByteCount,
|
||||
node_ref: NodeRef,
|
||||
safety_route: Option<DHTKey>,
|
||||
remote_private_route: Option<DHTKey>,
|
||||
reply_private_route: Option<DHTKey>,
|
||||
safety_route: Option<PublicKey>,
|
||||
remote_private_route: Option<PublicKey>,
|
||||
reply_private_route: Option<PublicKey>,
|
||||
) {
|
||||
// Record stats for remote node if this was direct
|
||||
if safety_route.is_none() && remote_private_route.is_none() && reply_private_route.is_none()
|
||||
@ -999,11 +1004,11 @@ impl RPCProcessor {
|
||||
dest: Destination,
|
||||
question: RPCQuestion,
|
||||
) -> Result<NetworkResult<WaitableReply>, RPCError> {
|
||||
// Get sender signed node info if we should send that
|
||||
let ssni = self.get_sender_signed_node_info(&dest)?;
|
||||
// Get sender peer info if we should send that
|
||||
let spi = self.get_sender_peer_info(&dest);
|
||||
|
||||
// Wrap question in operation
|
||||
let operation = RPCOperation::new_question(question, ssni);
|
||||
let operation = RPCOperation::new_question(question, spi);
|
||||
let op_id = operation.op_id();
|
||||
|
||||
// Log rpc send
|
||||
@ -1012,7 +1017,7 @@ impl RPCProcessor {
|
||||
// Produce rendered operation
|
||||
let RenderedOperation {
|
||||
message,
|
||||
node_id,
|
||||
destination_node_ref,
|
||||
node_ref,
|
||||
hop_count,
|
||||
safety_route,
|
||||
@ -1032,7 +1037,7 @@ impl RPCProcessor {
|
||||
let send_ts = get_aligned_timestamp();
|
||||
let send_data_kind = network_result_try!(self
|
||||
.network_manager()
|
||||
.send_envelope(node_ref.clone(), Some(node_id), message)
|
||||
.send_envelope(node_ref.clone(), Some(destination_node_ref), message)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// If we're returning an error, clean up
|
||||
@ -1074,11 +1079,11 @@ impl RPCProcessor {
|
||||
dest: Destination,
|
||||
statement: RPCStatement,
|
||||
) -> Result<NetworkResult<()>, RPCError> {
|
||||
// Get sender signed node info if we should send that
|
||||
let ssni = self.get_sender_signed_node_info(&dest)?;
|
||||
// Get sender peer info if we should send that
|
||||
let spi = self.get_sender_peer_info(&dest);
|
||||
|
||||
// Wrap statement in operation
|
||||
let operation = RPCOperation::new_statement(statement, ssni);
|
||||
let operation = RPCOperation::new_statement(statement, spi);
|
||||
|
||||
// Log rpc send
|
||||
trace!(target: "rpc_message", dir = "send", kind = "statement", op_id = operation.op_id().as_u64(), desc = operation.kind().desc(), ?dest);
|
||||
@ -1086,7 +1091,7 @@ impl RPCProcessor {
|
||||
// Produce rendered operation
|
||||
let RenderedOperation {
|
||||
message,
|
||||
node_id,
|
||||
destination_node_ref,
|
||||
node_ref,
|
||||
hop_count: _,
|
||||
safety_route,
|
||||
@ -1099,7 +1104,7 @@ impl RPCProcessor {
|
||||
let send_ts = get_aligned_timestamp();
|
||||
let _send_data_kind = network_result_try!(self
|
||||
.network_manager()
|
||||
.send_envelope(node_ref.clone(), Some(node_id), message)
|
||||
.send_envelope(node_ref.clone(), Some(destination_node_ref), message)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// If we're returning an error, clean up
|
||||
@ -1136,10 +1141,10 @@ impl RPCProcessor {
|
||||
let dest = network_result_try!(self.get_respond_to_destination(&request));
|
||||
|
||||
// Get sender signed node info if we should send that
|
||||
let ssni = self.get_sender_signed_node_info(&dest)?;
|
||||
let spi = self.get_sender_peer_info(&dest);
|
||||
|
||||
// Wrap answer in operation
|
||||
let operation = RPCOperation::new_answer(&request.operation, answer, ssni);
|
||||
let operation = RPCOperation::new_answer(&request.operation, answer, spi);
|
||||
|
||||
// Log rpc send
|
||||
trace!(target: "rpc_message", dir = "send", kind = "answer", op_id = operation.op_id().as_u64(), desc = operation.kind().desc(), ?dest);
|
||||
@ -1147,7 +1152,7 @@ impl RPCProcessor {
|
||||
// Produce rendered operation
|
||||
let RenderedOperation {
|
||||
message,
|
||||
node_id,
|
||||
destination_node_ref,
|
||||
node_ref,
|
||||
hop_count: _,
|
||||
safety_route,
|
||||
@ -1159,7 +1164,7 @@ impl RPCProcessor {
|
||||
let bytes: ByteCount = (message.len() as u64).into();
|
||||
let send_ts = get_aligned_timestamp();
|
||||
network_result_try!(self.network_manager()
|
||||
.send_envelope(node_ref.clone(), Some(node_id), message)
|
||||
.send_envelope(node_ref.clone(), Some(destination_node_ref), message)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// If we're returning an error, clean up
|
||||
@ -1197,7 +1202,10 @@ impl RPCProcessor {
|
||||
let routing_domain = detail.routing_domain;
|
||||
|
||||
// Decode the operation
|
||||
let sender_node_id = detail.envelope.get_sender_id();
|
||||
let sender_node_id = TypedKey::new(
|
||||
detail.envelope.get_crypto_kind(),
|
||||
detail.envelope.get_sender_id(),
|
||||
);
|
||||
|
||||
// Decode the RPC message
|
||||
let operation = {
|
||||
@ -1206,22 +1214,23 @@ impl RPCProcessor {
|
||||
.get_root::<veilid_capnp::operation::Reader>()
|
||||
.map_err(RPCError::protocol)
|
||||
.map_err(logthru_rpc!())?;
|
||||
RPCOperation::decode(&op_reader, Some(&sender_node_id))?
|
||||
RPCOperation::decode(&op_reader, self.crypto.clone())?
|
||||
};
|
||||
|
||||
// Get the sender noderef, incorporating and 'sender node info'
|
||||
// Get the sender noderef, incorporating sender's peer info
|
||||
let mut opt_sender_nr: Option<NodeRef> = None;
|
||||
if let Some(sender_node_info) = operation.sender_node_info() {
|
||||
// Sender NodeInfo was specified, update our routing table with it
|
||||
if !self.filter_node_info(routing_domain, &sender_node_info) {
|
||||
if let Some(sender_peer_info) = operation.sender_peer_info() {
|
||||
// Ensure the sender peer info is for the actual sender specified in the envelope
|
||||
|
||||
// Sender PeerInfo was specified, update our routing table with it
|
||||
if !self.filter_node_info(routing_domain, &sender_peer_info.signed_node_info) {
|
||||
return Err(RPCError::invalid_format(
|
||||
"sender signednodeinfo has invalid peer scope",
|
||||
"sender peerinfo has invalid peer scope",
|
||||
));
|
||||
}
|
||||
opt_sender_nr = self.routing_table().register_node_with_signed_node_info(
|
||||
opt_sender_nr = self.routing_table().register_node_with_peer_info(
|
||||
routing_domain,
|
||||
sender_node_id,
|
||||
sender_node_info.clone(),
|
||||
sender_peer_info.clone(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
@ -1252,7 +1261,7 @@ impl RPCProcessor {
|
||||
.get_root::<veilid_capnp::operation::Reader>()
|
||||
.map_err(RPCError::protocol)
|
||||
.map_err(logthru_rpc!())?;
|
||||
RPCOperation::decode(&op_reader, None)?
|
||||
RPCOperation::decode(&op_reader, self.crypto.clone())?
|
||||
};
|
||||
|
||||
// Make the RPC message
|
||||
@ -1386,15 +1395,17 @@ impl RPCProcessor {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, body), err)]
|
||||
pub fn enqueue_safety_routed_message(
|
||||
fn enqueue_safety_routed_message(
|
||||
&self,
|
||||
remote_safety_route: DHTKey,
|
||||
direct: RPCMessageHeaderDetailDirect,
|
||||
remote_safety_route: PublicKey,
|
||||
sequencing: Sequencing,
|
||||
body: Vec<u8>,
|
||||
) -> EyreResult<()> {
|
||||
let msg = RPCMessageEncoded {
|
||||
header: RPCMessageHeader {
|
||||
detail: RPCMessageHeaderDetail::SafetyRouted(RPCMessageHeaderDetailSafetyRouted {
|
||||
direct,
|
||||
remote_safety_route,
|
||||
sequencing,
|
||||
}),
|
||||
@ -1415,10 +1426,11 @@ impl RPCProcessor {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, body), err)]
|
||||
pub fn enqueue_private_routed_message(
|
||||
fn enqueue_private_routed_message(
|
||||
&self,
|
||||
remote_safety_route: DHTKey,
|
||||
private_route: DHTKey,
|
||||
direct: RPCMessageHeaderDetailDirect,
|
||||
remote_safety_route: PublicKey,
|
||||
private_route: PublicKey,
|
||||
safety_spec: SafetySpec,
|
||||
body: Vec<u8>,
|
||||
) -> EyreResult<()> {
|
||||
@ -1426,6 +1438,7 @@ impl RPCProcessor {
|
||||
header: RPCMessageHeader {
|
||||
detail: RPCMessageHeaderDetail::PrivateRouted(
|
||||
RPCMessageHeaderDetailPrivateRouted {
|
||||
direct,
|
||||
remote_safety_route,
|
||||
private_route,
|
||||
safety_spec,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user