Merge branch 'release/v1.1.31-kitkat'
This commit is contained in:
@@ -83,7 +83,7 @@ jobs:
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
run: make contracts-wasm
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
run: make contracts-wasm
|
||||
|
||||
- name: Upload Mixnet Contract Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
run: git fetch --all
|
||||
- name: Set output variable to latest release branch
|
||||
id: step2
|
||||
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
|
||||
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+-' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
|
||||
build:
|
||||
needs: [get_release,matrix_prep]
|
||||
strategy:
|
||||
|
||||
@@ -4,9 +4,11 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- "sdk/typescript/**"
|
||||
- "wasm/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "sdk/typescript/**"
|
||||
- "wasm/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,17 +28,39 @@ jobs:
|
||||
toolchain: stable
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Build branch WASM packages
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
- name: Build
|
||||
run: yarn docs:prod:build
|
||||
- name: Deploy branch to CI www (docs)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "dist/ts/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
NYM_PROJECT_NAME: "sdk-ts-docs"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}/docs/sdk/typescript"
|
||||
NYM_CI_WWW_LOCATION: "sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
|
||||
@@ -25,27 +25,37 @@ jobs:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
continue-on-error: true
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
|
||||
- name: Build packages
|
||||
run: yarn build
|
||||
run: yarn build:ci:sdk
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint && yarn tsc
|
||||
run: yarn lint
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/webassembly/**'
|
||||
- 'clients/client-core/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'gateway/gateway-requests/**'
|
||||
- 'nym-api/nym-api-requests/**'
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path clients/webassembly/Cargo.toml -- --check
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wasm/**'
|
||||
- 'clients/client-core/**'
|
||||
- 'common/**'
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Install wasm-bindgen-cli
|
||||
run: cargo install wasm-bindgen-cli
|
||||
|
||||
- name: "Build"
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: "Test"
|
||||
run: make sdk-wasm-test
|
||||
|
||||
- name: "Lint"
|
||||
run: make sdk-wasm-lint
|
||||
+3
-1
@@ -43,4 +43,6 @@ envs/qwerty.env
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
foxyfox.env
|
||||
foxyfox.env
|
||||
|
||||
.next
|
||||
@@ -4,6 +4,22 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.31-kitkat] (2023-09-12)
|
||||
|
||||
- feat: add name to `TaskClient` ([#3844])
|
||||
- added 'open_proxy', 'enabled_statistics' and 'statistics_recipient' to NR config ([#3839])
|
||||
- MixFetch: initial prototype for insecure HTTP ([#3645])
|
||||
- MixFetch: prototype implementing TLS in WASM for HTTPS ([#3644])
|
||||
- SDK: build package for NodeJS ([#3558])
|
||||
- [Issue] There is already an open connection to this client ([#2845])
|
||||
|
||||
[#3844]: https://github.com/nymtech/nym/pull/3844
|
||||
[#3839]: https://github.com/nymtech/nym/pull/3839
|
||||
[#3645]: https://github.com/nymtech/nym/issues/3645
|
||||
[#3644]: https://github.com/nymtech/nym/issues/3644
|
||||
[#3558]: https://github.com/nymtech/nym/issues/3558
|
||||
[#2845]: https://github.com/nymtech/nym/issues/2845
|
||||
|
||||
## [v1.1.30-twix] (2023-09-05)
|
||||
|
||||
- geo_aware_provider: fix too much filtering of gateways ([#3826])
|
||||
|
||||
Generated
+686
-36
File diff suppressed because it is too large
Load Diff
+21
-4
@@ -46,6 +46,7 @@ members = [
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/execute",
|
||||
"common/http-requests",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
@@ -73,7 +74,9 @@ members = [
|
||||
"common/task",
|
||||
"common/topology",
|
||||
"common/types",
|
||||
"common/wasm-utils",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
"explorer-api",
|
||||
"explorer-api/explorer-api-requests",
|
||||
"explorer-api/explorer-client",
|
||||
@@ -87,11 +90,18 @@ members = [
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"nym-api",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-outfox",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/sdk-version-bump",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-nr-query",
|
||||
"tools/ts-rs-cli"
|
||||
"tools/ts-rs-cli",
|
||||
"wasm/client",
|
||||
"wasm/full-nym-wasm",
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -105,7 +115,7 @@ default-members = [
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-browser-extension/storage", "cpu-cycles"]
|
||||
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Nym Technologies SA"]
|
||||
@@ -117,7 +127,7 @@ license = "Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.71"
|
||||
async-trait = "0.1.64"
|
||||
async-trait = "0.1.68"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.3.0"
|
||||
@@ -156,4 +166,11 @@ url = "2.4"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.1.7"
|
||||
js-sys = "0.3.63"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.86"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.63"
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
# Default target
|
||||
all: test
|
||||
|
||||
test: clippy-all cargo-test wasm fmt
|
||||
test: clippy-all cargo-test contracts-wasm sdk-wasm-test fmt
|
||||
|
||||
test-all: test cargo-test-expensive
|
||||
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
no-clippy: build cargo-test contracts-wasm fmt fmt-browser-extension-storage
|
||||
|
||||
happy: fmt clippy-happy test
|
||||
|
||||
build: sdk-wasm-build build-browser-extension-storage
|
||||
|
||||
# Building release binaries is a little manual as we can't just build --release
|
||||
# on all workspaces.
|
||||
build-release: build-release-main wasm
|
||||
build-release: build-release-main contracts-wasm
|
||||
|
||||
clippy: sdk-wasm-lint clippy-browser-extension-storage
|
||||
|
||||
# Deprecated
|
||||
# For backwards compatibility
|
||||
@@ -43,6 +47,9 @@ test-$(1):
|
||||
test-expensive-$(1):
|
||||
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
|
||||
|
||||
build-standalone-$(1):
|
||||
cargo build --manifest-path $(2)/Cargo.toml $(3)
|
||||
|
||||
build-$(1):
|
||||
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
|
||||
|
||||
@@ -74,7 +81,7 @@ endef
|
||||
|
||||
$(eval $(call add_cargo_workspace,main,.))
|
||||
$(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
#$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
|
||||
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
|
||||
|
||||
@@ -88,6 +95,67 @@ build-explorer-api:
|
||||
build-nym-cli:
|
||||
cargo build -p nym-cli --release
|
||||
|
||||
build-browser-extension-storage:
|
||||
cargo build -p extension-storage --target wasm32-unknown-unknown
|
||||
|
||||
fmt-browser-extension-storage:
|
||||
cargo fmt -p extension-storage -- --check
|
||||
|
||||
clippy-browser-extension-storage:
|
||||
cargo clippy -p extension-storage --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
|
||||
|
||||
sdk-wasm-build:
|
||||
# browser storage
|
||||
$(MAKE) -C nym-browser-extension/storage wasm-pack
|
||||
|
||||
# client
|
||||
$(MAKE) -C wasm/client build
|
||||
|
||||
# node-tester
|
||||
$(MAKE) -C wasm/node-tester build
|
||||
|
||||
# mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch build
|
||||
|
||||
# full
|
||||
$(MAKE) -C wasm/full-nym-wasm build-full
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
sdk-typescript-build:
|
||||
lerna run --scope @nymproject/sdk build --stream
|
||||
lerna run --scope @nymproject/mix-fetch build --stream
|
||||
lerna run --scope @nymproject/node-tester build --stream
|
||||
|
||||
sdk-wasm-test:
|
||||
# # client
|
||||
# cargo test -p nym-client-wasm --target wasm32-unknown-unknown
|
||||
#
|
||||
# # node-tester
|
||||
# cargo test -p nym-node-tester-wasm --target wasm32-unknown-unknown
|
||||
#
|
||||
# # mix-fetch
|
||||
# #cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
|
||||
#
|
||||
# # full
|
||||
# cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
|
||||
|
||||
|
||||
sdk-wasm-lint:
|
||||
# client
|
||||
cargo clippy -p nym-client-wasm --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# node-tester
|
||||
cargo clippy -p nym-node-tester-wasm --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch check-fmt
|
||||
|
||||
# full
|
||||
cargo clippy -p nym-wasm-sdk --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build contracts ready for deploy
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -98,12 +166,12 @@ MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
|
||||
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
|
||||
|
||||
wasm: wasm-build wasm-opt
|
||||
contracts-wasm: contracts-wasm-build contracts-wasm-opt
|
||||
|
||||
wasm-build:
|
||||
contracts-wasm-build:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --lib --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
|
||||
wasm-opt:
|
||||
contracts-wasm-opt:
|
||||
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
|
||||
@@ -117,7 +185,7 @@ contract-schema:
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# NOTE: this seems deprecated an not needed anymore?
|
||||
mixnet-opt: wasm
|
||||
mixnet-opt: contracts-wasm
|
||||
cd contracts/mixnet && make opt
|
||||
|
||||
generate-typescript:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.28"
|
||||
version = "1.1.29"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::persistence::ClientPaths;
|
||||
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
|
||||
use crate::{
|
||||
client::config::{
|
||||
default_config_filepath, persistence::ClientPaths, Config, Socket, SocketType,
|
||||
},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2;
|
||||
@@ -43,18 +48,18 @@ impl ConfigV1_1_20_2 {
|
||||
|
||||
// in this upgrade, gateway endpoint configuration was moved out of the config file,
|
||||
// so its returned to be stored elsewhere.
|
||||
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
|
||||
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), ClientError> {
|
||||
let gateway_details = self.base.client.gateway_endpoint.clone().into();
|
||||
let config = Config {
|
||||
base: self.base.into(),
|
||||
socket: self.socket.into(),
|
||||
storage_paths: ClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default(),
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
};
|
||||
|
||||
(config, gateway_details)
|
||||
Ok((config, gateway_details))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
|
||||
|
||||
let updated_step1: ConfigV1_1_20 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated, gateway_config) = updated_step2.upgrade();
|
||||
let (updated, gateway_config) = updated_step2.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -177,7 +177,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated, gateway_config) = updated_step1.upgrade();
|
||||
let (updated, gateway_config) = updated_step1.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -194,7 +194,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
|
||||
info!("It seems the client is using <= v1.1.20_2 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let (updated, gateway_config) = old_config.upgrade();
|
||||
let (updated, gateway_config) = old_config.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.28"
|
||||
version = "1.1.29"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -199,7 +199,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
|
||||
let updated_step1: ConfigV1_1_20 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated, gateway_config) = updated_step2.upgrade();
|
||||
let (updated, gateway_config) = updated_step2.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -219,7 +219,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated, gateway_config) = updated_step1.upgrade();
|
||||
let (updated, gateway_config) = updated_step1.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -236,7 +236,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
info!("It seems the client is using <= v1.1.20_2 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let (updated, gateway_config) = old_config.upgrade();
|
||||
let (updated, gateway_config) = old_config.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::SocksClientPaths;
|
||||
use crate::config::{default_config_filepath, Config};
|
||||
use crate::{
|
||||
config::{default_config_filepath, persistence::SocksClientPaths, Config},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct SocksClientPathsV1_1_20_2 {
|
||||
#[serde(flatten)]
|
||||
@@ -39,16 +43,16 @@ impl ConfigV1_1_20_2 {
|
||||
|
||||
// in this upgrade, gateway endpoint configuration was moved out of the config file,
|
||||
// so its returned to be stored elsewhere.
|
||||
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
|
||||
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), Socks5ClientError> {
|
||||
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
|
||||
let config = Config {
|
||||
core: self.core.into(),
|
||||
storage_paths: SocksClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default(),
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
};
|
||||
|
||||
(config, gateway_details)
|
||||
Ok((config, gateway_details))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --locked
|
||||
@@ -1,2 +0,0 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -1,6 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
node_modules
|
||||
@@ -1,69 +0,0 @@
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
# Builds with wasm-pack.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
addons:
|
||||
firefox: latest
|
||||
chrome: stable
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
|
||||
# in any of our parent dirs is problematic.
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- wasm-pack build
|
||||
- wasm-pack test --chrome --firefox --headless
|
||||
|
||||
# Builds on nightly.
|
||||
- rust: nightly
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
|
||||
# Builds on beta.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
# Note: no enabling the `wee_alloc` feature here because it requires
|
||||
# nightly for now.
|
||||
Generated
-5559
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "1.1.1"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
bs58 = "0.4.0"
|
||||
futures = "0.3"
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
tokio = { version = "1.24.1", features = ["sync"] }
|
||||
url = "2.2"
|
||||
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
wasmtimer = { version = "0.2.0", features = ["tokio"] }
|
||||
|
||||
# internal
|
||||
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
|
||||
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../../common/task" }
|
||||
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
#
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
@@ -1,176 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,8 +0,0 @@
|
||||
clippy:
|
||||
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
test:
|
||||
wasm-pack test --node
|
||||
|
||||
wasm-build:
|
||||
wasm-pack build
|
||||
@@ -1,382 +0,0 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
importScripts("nym_client_wasm.js");
|
||||
|
||||
console.log("Initializing worker");
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const {
|
||||
NymNodeTester,
|
||||
WasmGateway,
|
||||
WasmMixNode,
|
||||
WasmNymTopology,
|
||||
default_debug,
|
||||
NymClientBuilder,
|
||||
NymClient,
|
||||
set_panic_hook,
|
||||
Config,
|
||||
GatewayEndpointConfig,
|
||||
ClientStorage,
|
||||
current_network_topology,
|
||||
make_key,
|
||||
make_key2,
|
||||
} = wasm_bindgen;
|
||||
|
||||
let client = null;
|
||||
let tester = null;
|
||||
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
|
||||
function dummyTopology() {
|
||||
const l1Mixnode = new WasmMixNode(
|
||||
1,
|
||||
"n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47",
|
||||
"178.79.143.65",
|
||||
1789,
|
||||
"4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz",
|
||||
"8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG",
|
||||
1,
|
||||
"1.10.0"
|
||||
);
|
||||
const l2Mixnode = new WasmMixNode(
|
||||
2,
|
||||
"n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70",
|
||||
"109.74.197.180",
|
||||
1789,
|
||||
"7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK",
|
||||
"GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN",
|
||||
2,
|
||||
"1.10.0"
|
||||
);
|
||||
const l3Mixnode = new WasmMixNode(
|
||||
3,
|
||||
"n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77",
|
||||
"176.58.101.80",
|
||||
1789,
|
||||
"FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R",
|
||||
"DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD",
|
||||
3,
|
||||
"1.10.0"
|
||||
);
|
||||
|
||||
const gateway = new WasmGateway(
|
||||
"n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts",
|
||||
"85.159.212.96",
|
||||
1789,
|
||||
9000,
|
||||
"336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9",
|
||||
"BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL",
|
||||
"1.10.1"
|
||||
);
|
||||
|
||||
const mixnodes = new Map();
|
||||
mixnodes.set(1, [l1Mixnode]);
|
||||
mixnodes.set(2, [l2Mixnode]);
|
||||
mixnodes.set(3, [l3Mixnode]);
|
||||
|
||||
const gateways = [gateway];
|
||||
|
||||
return new WasmNymTopology(mixnodes, gateways);
|
||||
}
|
||||
|
||||
function printAndDisplayTestResult(result) {
|
||||
result.log_details();
|
||||
|
||||
self.postMessage({
|
||||
kind: "DisplayTesterResults",
|
||||
args: {
|
||||
score: result.score(),
|
||||
sentPackets: result.sent_packets,
|
||||
receivedPackets: result.received_packets,
|
||||
receivedAcks: result.received_acks,
|
||||
duplicatePackets: result.duplicate_packets,
|
||||
duplicateAcks: result.duplicate_acks,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function testWithTester() {
|
||||
// A) construct with hardcoded topology
|
||||
const topology = dummyTopology();
|
||||
const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(topology, undefined, preferredGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, undefined, preferredGateway)
|
||||
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
|
||||
// const topology = dummyTopology()
|
||||
// const nodeTester = await new NymNodeTester(topology);
|
||||
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("starting node test...");
|
||||
|
||||
let result = await nodeTester.test_node(mixnodeIdentity);
|
||||
printAndDisplayTestResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function testerReconnection() {
|
||||
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
|
||||
const nodeTester = await NymNodeTester.new_with_api(validator);
|
||||
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("starting node test...");
|
||||
|
||||
let result1 = await nodeTester.test_node(mixnodeIdentity);
|
||||
console.log("sleeping for 5s");
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
await nodeTester.disconnect_from_gateway();
|
||||
|
||||
console.log("sleeping for 5s");
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
|
||||
await nodeTester.reconnect_to_gateway();
|
||||
let result2 = await nodeTester.test_node(mixnodeIdentity);
|
||||
|
||||
printAndDisplayTestResult(result1);
|
||||
printAndDisplayTestResult(result2);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function testWithNymClient() {
|
||||
const topology = dummyTopology();
|
||||
|
||||
let received = 0;
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
received += 1;
|
||||
self.postMessage({
|
||||
kind: "ReceiveMessage",
|
||||
args: {
|
||||
message,
|
||||
senderTag: undefined,
|
||||
isTestPacket: true,
|
||||
},
|
||||
});
|
||||
|
||||
// it's really up to the user to create proper callback here...
|
||||
console.log(`received ${received} packets so far`);
|
||||
};
|
||||
|
||||
console.log("Instantiating WASM client...");
|
||||
|
||||
let clientBuilder = NymClientBuilder.new_tester(
|
||||
topology,
|
||||
onMessageHandler,
|
||||
preferredGateway
|
||||
);
|
||||
|
||||
console.log("Web worker creating WASM client...");
|
||||
let local_client = await clientBuilder.start_client();
|
||||
console.log("WASM client running!");
|
||||
|
||||
const selfAddress = local_client.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = local_client;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: "Ready",
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async (event) => {
|
||||
console.log(event);
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "SendMessage": {
|
||||
const { message, recipient } = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
const req = await client.try_construct_test_packet_request(
|
||||
mixnodeIdentity
|
||||
);
|
||||
await client.change_hardcoded_topology(req.injectable_topology());
|
||||
await client.try_send_test_packets(req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function normalNymClientUsage() {
|
||||
self.postMessage({ kind: "DisableMagicTestButton" });
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
const debug = default_debug();
|
||||
|
||||
debug.disable_main_poisson_packet_distribution = true;
|
||||
debug.disable_loop_cover_traffic_stream = true;
|
||||
debug.use_extended_packet_size = false;
|
||||
// debug.average_packet_delay_ms = BigInt(10);
|
||||
// debug.average_ack_delay_ms = BigInt(10);
|
||||
// debug.ack_wait_addition_ms = BigInt(3000);
|
||||
// debug.ack_wait_multiplier = 10;
|
||||
|
||||
debug.topology_refresh_rate_ms = BigInt(60000);
|
||||
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
|
||||
|
||||
const config = new Config("my-awesome-wasm-client", validator, debug);
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
console.log(message);
|
||||
self.postMessage({
|
||||
kind: "ReceiveMessage",
|
||||
args: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
console.log("Instantiating WASM client...");
|
||||
|
||||
let localClient = await new NymClient(config, onMessageHandler);
|
||||
console.log("WASM client running!");
|
||||
|
||||
const selfAddress = localClient.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = localClient;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: "Ready",
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async (event) => {
|
||||
console.log(event);
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "SendMessage": {
|
||||
const { message, recipient } = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function messWithStorage() {
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("button clicked...", mixnodeIdentity);
|
||||
|
||||
let id1 = "one";
|
||||
let id2 = "two";
|
||||
|
||||
console.log("making store1 NO-ENC");
|
||||
let _storage1 = await ClientStorage.new_unencrypted(id1);
|
||||
|
||||
console.log("making store2 ENC");
|
||||
let _storage2 = await new ClientStorage(id2, "my-secret-password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store1 WITH PASSWORD")
|
||||
// let _storage1_alt = await new ClientStorage(id1, "password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 WITHOUT PASSWORD")
|
||||
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 with WRONG PASSWORD")
|
||||
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
|
||||
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
//
|
||||
// console.log("store1: ", await storage1.store("FOOMP"));
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// load WASM package
|
||||
await wasm_bindgen("nym_client_wasm_bg.wasm");
|
||||
console.log("Loaded WASM");
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
// show reconnection capabilities
|
||||
// await testerReconnection()
|
||||
|
||||
// run test on simplified and dedicated tester:
|
||||
await testWithTester();
|
||||
|
||||
// 'Normal' client setup (to send 'normal' messages)
|
||||
// await normalNymClientUsage()
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
|
||||
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
|
||||
let validator_client =
|
||||
nym_validator_client::client::NymApiClient::new(api_server.parse().unwrap());
|
||||
|
||||
let gateways = match validator_client.get_cached_gateways().await {
|
||||
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
if let Some(preferred) = preferred {
|
||||
if let Some(details) = gateways
|
||||
.iter()
|
||||
.find(|g| g.gateway.identity_key == preferred)
|
||||
{
|
||||
return GatewayEndpointConfig {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let details = gateways
|
||||
.first()
|
||||
.expect("current topology holds no gateways");
|
||||
|
||||
GatewayEndpointConfig {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// After reading https://github.com/rust-lang/rust-clippy/issues/11382
|
||||
// I suspect we *maybe* have hit a false positive, but I'm not sure.
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod encoded_payload_helper;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod gateway_selector;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod storage;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod tester;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod topology;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod validation;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod helpers;
|
||||
|
||||
mod constants;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
use wasm_utils::storage::error::StorageError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientStorageError {
|
||||
#[error("failed to use the storage: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("{typ} cryptographic key is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
|
||||
#[error("the prior gateway details are not available in the storage")]
|
||||
GatewayDetailsNotInStorage,
|
||||
}
|
||||
|
||||
impl From<ClientStorageError> for JsValue {
|
||||
fn from(value: ClientStorageError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::storage::error::ClientStorageError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_utils::PromisableResult;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod traits;
|
||||
|
||||
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
|
||||
const STORAGE_VERSION: u32 = 1;
|
||||
|
||||
// v1 tables
|
||||
mod v1 {
|
||||
// stores
|
||||
pub const KEYS_STORE: &str = "keys";
|
||||
pub const CORE_STORE: &str = "core";
|
||||
|
||||
// keys
|
||||
pub const CONFIG: &str = "config";
|
||||
pub const GATEWAY_DETAILS: &str = "gateway_details";
|
||||
|
||||
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
|
||||
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
|
||||
|
||||
// TODO: for those we could actually use the subtle crypto storage
|
||||
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
|
||||
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: Arc<WasmStorage>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClientStorage {
|
||||
fn db_name(client_id: &str) -> String {
|
||||
format!("{STORAGE_NAME_PREFIX}-{client_id}")
|
||||
}
|
||||
|
||||
pub(crate) async fn new_async(
|
||||
client_id: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<Self, ClientStorageError> {
|
||||
let name = Self::db_name(client_id);
|
||||
|
||||
// make sure the password is zeroized when no longer used, especially if we error out.
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let inner = WasmStorage::new(
|
||||
&name,
|
||||
STORAGE_VERSION,
|
||||
migrate_fn,
|
||||
passphrase.as_ref().map(|p| p.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage {
|
||||
inner: Arc::new(inner),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(client_id: String, passphrase: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, Some(passphrase))
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unencrypted(client_id: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, None)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: persist client's config
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn read_config(&self) -> Result<Option<Config>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn may_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<Option<PersistedGatewayDetails>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn must_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<PersistedGatewayDetails, ClientStorageError> {
|
||||
self.may_read_gateway_details()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::GatewayDetailsNotInStorage)
|
||||
}
|
||||
|
||||
async fn may_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
|
||||
self.may_read_identity_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<encryption::KeyPair, ClientStorageError> {
|
||||
self.may_read_encryption_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
|
||||
self.may_read_ack_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_ACK_KEY.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
|
||||
self.may_read_gateway_shared_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_encryption_keypair(
|
||||
&self,
|
||||
keypair: &encryption::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_ACK_KEY),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn store_gateway_details(
|
||||
&self,
|
||||
gateway_endpoint: &PersistedGatewayDetails,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::CORE_STORE,
|
||||
JsValue::from_str(v1::GATEWAY_DETAILS),
|
||||
gateway_endpoint,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// TODO: persist client's config
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn store_config(&self, config: &Config) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG), config)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn has_full_gateway_info(&self) -> Result<bool, ClientStorageError> {
|
||||
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
|
||||
let has_details = self.may_read_gateway_details().await?.is_some();
|
||||
|
||||
Ok(has_keys && has_details)
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::mix::{Layer, MixnodeConversionError};
|
||||
use nym_topology::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_validator_client::client::{IdentityKeyRef, MixId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::{console_log, simple_js_error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmTopologyError {
|
||||
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
|
||||
InvalidMixLayer { value: u8 },
|
||||
|
||||
#[error(transparent)]
|
||||
GatewayConversion(#[from] GatewayConversionError),
|
||||
|
||||
#[error(transparent)]
|
||||
MixnodeConversion(#[from] MixnodeConversionError),
|
||||
|
||||
#[error("The provided mixnode map was malformed: {source}")]
|
||||
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
|
||||
|
||||
#[error("The provided gateway list was malformed: {source}")]
|
||||
MalformedGatewayList { source: serde_wasm_bindgen::Error },
|
||||
}
|
||||
|
||||
impl From<WasmTopologyError> for JsValue {
|
||||
fn from(value: WasmTopologyError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct WasmNymTopology {
|
||||
inner: NymTopology,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmNymTopology {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
|
||||
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
|
||||
mixnodes: JsValue,
|
||||
// expected: Vec<WasmGateway>
|
||||
gateways: JsValue,
|
||||
) -> Result<WasmNymTopology, WasmTopologyError> {
|
||||
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
|
||||
serde_wasm_bindgen::from_value(mixnodes)
|
||||
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
|
||||
|
||||
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
|
||||
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
|
||||
|
||||
let mut converted_mixes = BTreeMap::new();
|
||||
|
||||
for (layer, nodes) in mixnodes {
|
||||
let layer_nodes = nodes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
converted_mixes.insert(layer, layer_nodes);
|
||||
}
|
||||
|
||||
let gateways = gateways
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(WasmNymTopology {
|
||||
inner: NymTopology::new(converted_mixes, gateways),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
|
||||
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
|
||||
self.inner
|
||||
.gateways()
|
||||
.iter()
|
||||
.any(|g| g.identity_key.to_base58_string() == gateway_id)
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
if !self.inner.mixes().is_empty() {
|
||||
console_log!("mixnodes:");
|
||||
for (layer, nodes) in self.inner.mixes() {
|
||||
console_log!("\tlayer {layer}:");
|
||||
for node in nodes {
|
||||
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console_log!("NO MIXNODES")
|
||||
}
|
||||
|
||||
if !self.inner.gateways().is_empty() {
|
||||
console_log!("gateways:");
|
||||
for gateway in self.inner.gateways() {
|
||||
console_log!("\t{}", gateway.identity_key)
|
||||
}
|
||||
} else {
|
||||
console_log!("NO GATEWAYS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmNymTopology> for NymTopology {
|
||||
fn from(value: WasmNymTopology) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopology> for WasmNymTopology {
|
||||
fn from(value: NymTopology) -> Self {
|
||||
WasmNymTopology { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmMixNode {
|
||||
pub mix_id: MixId,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
pub layer: MixLayer,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmMixNode {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mix_id: MixId,
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
layer: MixLayer,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_id,
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
layer,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmMixNode> for mix::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
|
||||
let host = mix::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(mix::Node {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
layer: Layer::try_from(value.layer)
|
||||
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmGateway {
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub clients_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmGateway {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
clients_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
clients_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmGateway> for gateway::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
|
||||
let host = gateway::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(gateway::Node {
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
clients_port: value.clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn validate_recipient(recipient: String) -> Result<(), JsError> {
|
||||
match Recipient::try_from_base58_string(recipient) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsError::new(format!("{}", e).as_str())),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::validate_recipient;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_recipient_validation_ok() {
|
||||
let res = validate_recipient("DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string());
|
||||
assert!(res.is_ok())
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_recipient_validation_fails() {
|
||||
assert!(validate_recipient(" DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()).is_err());
|
||||
assert!(validate_recipient(
|
||||
"DyQmXnst5NGGjzUZxRC5BjbRiizr2.GH6YTBP2NXU3AVqd8WD@336yuXAeGEgedRfqTJZQH1bHv1SjCZYarc9"
|
||||
.to_string()
|
||||
)
|
||||
.is_err());
|
||||
assert!(validate_recipient("🙀🙀🙀🙀".to_string()).is_err());
|
||||
assert!(validate_recipient("".to_string()).is_err());
|
||||
assert!(validate_recipient(" ".to_string()).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
@@ -22,11 +22,11 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = "0.10.6"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
time = "0.3.17"
|
||||
tokio = { version = "1.24.1", features = ["macros"]}
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
tokio = { workspace = true, features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
@@ -51,7 +51,7 @@ version = "0.1.11"
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||
@@ -63,10 +63,10 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.83"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
@@ -77,7 +77,7 @@ version = "0.2.4"
|
||||
features = ["futures"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../wasm-utils"
|
||||
path = "../wasm/utils"
|
||||
features = ["websocket"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
|
||||
@@ -88,7 +88,7 @@ features = ["wasm-bindgen"]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -6,7 +6,7 @@ use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
// TODO: this should now live inside our wasm/client-core
|
||||
pub mod browser_backend;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
|
||||
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
|
||||
use crate::error::ClientCoreError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -15,16 +16,17 @@ pub struct CommonClientPathsV1_1_20_2 {
|
||||
}
|
||||
|
||||
impl CommonClientPathsV1_1_20_2 {
|
||||
pub fn upgrade_default(self) -> CommonClientPaths {
|
||||
let data_dir = self
|
||||
.reply_surb_database
|
||||
.parent()
|
||||
.expect("client paths upgrade failure");
|
||||
CommonClientPaths {
|
||||
pub fn upgrade_default(self) -> Result<CommonClientPaths, ClientCoreError> {
|
||||
let data_dir = self.reply_surb_database.parent().ok_or_else(|| {
|
||||
ClientCoreError::UnableToUpgradeConfigFile {
|
||||
new_version: "1.1.20-2".to_string(),
|
||||
}
|
||||
})?;
|
||||
Ok(CommonClientPaths {
|
||||
keys: self.keys,
|
||||
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
|
||||
credentials_database: self.credentials_database,
|
||||
reply_surb_database: self.reply_surb_database,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,11 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S, version: S) -> Self {
|
||||
pub fn new<S1, S2>(id: S1, version: S2) -> Self
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
Config {
|
||||
client: Client::new_default(id, version),
|
||||
debug: Default::default(),
|
||||
@@ -287,7 +291,11 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new_default<S: Into<String>>(id: S, version: S) -> Self {
|
||||
pub fn new_default<S1, S2>(id: S1, version: S2) -> Self
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
let network = NymNetworkDetails::new_mainnet();
|
||||
let nyxd_urls = network
|
||||
.endpoints
|
||||
|
||||
@@ -119,6 +119,9 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
|
||||
MismatchedGatewayDetails { gateway_id: String },
|
||||
|
||||
#[error("unable to upgrade config file to `{new_version}`")]
|
||||
UnableToUpgradeConfigFile { new_version: String },
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
|
||||
@@ -106,14 +106,17 @@ pub enum GatewaySetup {
|
||||
/// Should the new gateway be selected based on latency.
|
||||
by_latency: bool,
|
||||
},
|
||||
|
||||
Specified {
|
||||
/// Identity key of the gateway we want to try to use.
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
Predefined {
|
||||
/// Full gateway configuration
|
||||
details: PersistedGatewayDetails,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: GatewayClient<InitOnly>,
|
||||
|
||||
@@ -36,7 +36,7 @@ default-features = false
|
||||
|
||||
# non-wasm-only dependencies
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = ["macros", "rt", "net", "sync", "time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||
@@ -48,15 +48,18 @@ version = "0.14"
|
||||
|
||||
# wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../../wasm-utils"
|
||||
path = "../../wasm/utils"
|
||||
features = ["websocket"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-utils]
|
||||
workspace = true
|
||||
|
||||
# only import it in wasm. Prefer proper tokio timer in non-wasm
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
@@ -68,7 +71,7 @@ features = ["tokio"]
|
||||
# containing javascript (such as a web browser or node.js).
|
||||
# refer to https://docs.rs/getrandom/0.2.2/getrandom/#webassembly-support for more information
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom]
|
||||
version = "0.2"
|
||||
workspace = true
|
||||
features = ["js"]
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -141,8 +141,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => {
|
||||
(*socket).close(None).await;
|
||||
SocketState::Available(socket) => {
|
||||
(*socket).close(None, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
@@ -175,7 +175,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
|
||||
Err(e) => {
|
||||
return Err(GatewayClientError::NetworkErrorWasm(e));
|
||||
}
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gloo_utils::errors::JsError;
|
||||
use nym_gateway_requests::registration::handshake::error::HandshakeError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use tungstenite::Error as WsError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayClientError {
|
||||
@@ -19,10 +19,9 @@ pub enum GatewayClientError {
|
||||
#[error("There was a network error - {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
|
||||
// TODO: see if `JsValue` is a reasonable type for this
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("There was a network error")]
|
||||
NetworkErrorWasm(JsValue),
|
||||
NetworkErrorWasm(#[from] JsError),
|
||||
|
||||
#[error("Invalid URL - {0}")]
|
||||
InvalidURL(String),
|
||||
|
||||
@@ -5,9 +5,10 @@ use crate::backends::memory::CoconutCredentialManager;
|
||||
use crate::error::StorageError;
|
||||
use crate::models::CoconutCredential;
|
||||
use crate::storage::Storage;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub type EphemeralCredentialStorage = EphemeralStorage;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct EphemeralStorage {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nym-http-requests"
|
||||
version = "0.1.0"
|
||||
description = "Helper library for sending HTTP requesters over the Nym mixnet"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nym-socks5-requests = { path = "../socks5/requests" }
|
||||
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
|
||||
nym-service-providers-common = { path = "../../service-providers/common" }
|
||||
bytecodec = "0.4.15"
|
||||
httpcodec = "0.2.3"
|
||||
bytes = "1"
|
||||
http = "0.2.9"
|
||||
thiserror = "1"
|
||||
url = "2"
|
||||
@@ -0,0 +1,23 @@
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MixHttpRequestError {
|
||||
#[error("invalid Socks5 response")]
|
||||
InvalidSocks5Response,
|
||||
|
||||
#[error("the received Socks5 response was empty")]
|
||||
EmptySocks5Response,
|
||||
|
||||
#[error("bytecodec Error: {0}")]
|
||||
ByteCodecError(#[from] bytecodec::Error),
|
||||
|
||||
#[error("Url parse error: {0}")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("could not resolve socket address from the provided URL")]
|
||||
SocketAddrResolveError {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::Encode;
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
pub fn encode_http_request_as_string(
|
||||
request: Request<Vec<u8>>,
|
||||
) -> Result<String, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod http_requests_tests {
|
||||
use super::*;
|
||||
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
|
||||
fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
let mut request = Request::new(
|
||||
Method::new("GET").unwrap(),
|
||||
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
HttpVersion::V1_1,
|
||||
b"".to_vec(),
|
||||
);
|
||||
let mut headers = request.header_mut();
|
||||
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_request_ok() {
|
||||
// Encode HTTP request as bytes
|
||||
let request = create_http_get_request();
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf).unwrap();
|
||||
|
||||
let body_as_string = String::from_utf8(buf).unwrap();
|
||||
|
||||
// replace newlines with \r\n
|
||||
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
|
||||
Host: nymtech.net
|
||||
Content-Length: 0
|
||||
|
||||
"
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
assert_eq!(expected, body_as_string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error;
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::bytes::RemainingBytesDecoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::{DecodeExt, Encode};
|
||||
use httpcodec::{BodyDecoder, ResponseDecoder};
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
use nym_service_providers_common::interface::ProviderInterfaceVersion;
|
||||
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
|
||||
|
||||
pub fn encode_http_request_as_socks_send_request(
|
||||
provider_interface: ProviderInterfaceVersion,
|
||||
socks5_protocol: Socks5ProtocolVersion,
|
||||
conn_id: u64,
|
||||
request: Request<Vec<u8>>,
|
||||
seq: Option<u64>,
|
||||
local_closed: bool,
|
||||
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
// Wrap it as SOCKS send request
|
||||
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
|
||||
socks5_protocol,
|
||||
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
|
||||
);
|
||||
|
||||
// and wrap it in provider request
|
||||
Ok(Socks5ProviderRequest::new_provider_data(
|
||||
provider_interface,
|
||||
request_content,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MixHttpResponse {
|
||||
// pub connection_id: u64,
|
||||
// #[deprecated]
|
||||
// pub is_closed: bool,
|
||||
pub http_response: httpcodec::Response<Vec<u8>>,
|
||||
// #[deprecated]
|
||||
// pub seq: u64,
|
||||
}
|
||||
|
||||
impl MixHttpResponse {
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
if b.is_empty() {
|
||||
Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
} else {
|
||||
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
let http_response = decoder.decode_from_bytes(b)?;
|
||||
|
||||
Ok(MixHttpResponse { http_response })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl TryFrom<Socks5Response> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
|
||||
// if let Socks5ResponseContent::NetworkData { content } = value.content {
|
||||
// content.try_into()
|
||||
// } else {
|
||||
// Err(error::MixHttpRequestError::InvalidSocks5Response)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl TryFrom<SocketData> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
|
||||
// if value.data.is_empty() {
|
||||
// Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
// } else {
|
||||
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
|
||||
//
|
||||
// Ok(MixHttpResponse {
|
||||
// connection_id: value.header.connection_id,
|
||||
// is_closed: value.header.local_socket_closed,
|
||||
// http_response,
|
||||
// seq: value.header.seq,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn decode_socks_response_as_http_response(
|
||||
// socks5_response: Socks5Response,
|
||||
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
// socks5_response.try_into()
|
||||
// }
|
||||
//
|
||||
// #[cfg(test)]
|
||||
// mod http_requests_tests {
|
||||
// use super::*;
|
||||
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
// use nym_service_providers_common::interface::Serializable;
|
||||
// use nym_socks5_requests::Socks5Response;
|
||||
//
|
||||
// fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
// let mut request = Request::new(
|
||||
// Method::new("GET").unwrap(),
|
||||
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
// HttpVersion::V1_1,
|
||||
// b"".to_vec(),
|
||||
// );
|
||||
// let mut headers = request.header_mut();
|
||||
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
//
|
||||
// request
|
||||
// }
|
||||
//
|
||||
// fn create_socks5_request_buffer() -> Vec<u8> {
|
||||
// let request = create_http_get_request();
|
||||
// let socks5_request = encode_http_request_as_socks_send_request(
|
||||
// ProviderInterfaceVersion::new_current(),
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// 99u64,
|
||||
// request,
|
||||
// Some(42u64),
|
||||
// true,
|
||||
// )
|
||||
// .unwrap();
|
||||
// socks5_request.into_bytes()
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_http_request_content_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // HTTP request string content is as expected
|
||||
// assert_eq!(
|
||||
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
|
||||
// buffer[19..27]
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
|
||||
// /// changes to have the `index` value as a field, instead of packed with the `data`
|
||||
// #[test]
|
||||
// fn request_size_as_expected_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
|
||||
//
|
||||
// assert_eq!(108, buffer.len()); // version set to SOCKS5
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_socks5_headers_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
|
||||
// assert_eq!(1u8, buffer[1]); // type is SEND
|
||||
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
|
||||
// assert_eq!(1u8, buffer[10]); // local_closed is true
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_ordered_message_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // OrderedMessage index is correct
|
||||
// assert_eq!(42u8, buffer[18]);
|
||||
// }
|
||||
//
|
||||
// fn create_socks_response() -> Socks5Response {
|
||||
// // HTTP response is just a string
|
||||
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
|
||||
//
|
||||
// let data = http_response_string.as_bytes().to_vec();
|
||||
//
|
||||
// // wrap in `NetworkData`, then Socks5Response
|
||||
// Socks5Response::new(
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// Socks5ResponseContent::NetworkData {
|
||||
// content: SocketData::new(42, 99u64, false, data),
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /// This test will fail is anything in the framing of the socks5_response byte
|
||||
// /// representation changes
|
||||
// #[test]
|
||||
// fn response_byte_size_is_as_expected() {
|
||||
// let socks5_response = create_socks_response();
|
||||
// let buf = socks5_response.into_bytes();
|
||||
//
|
||||
// assert_eq!(57, buf.len());
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn response_parses() {
|
||||
// unimplemented!()
|
||||
// // let socks5_response = create_socks_response();
|
||||
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
|
||||
// //
|
||||
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
|
||||
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
|
||||
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
|
||||
// // assert_eq!(
|
||||
// // "foo/0.0.1",
|
||||
// // response.http_response.header().get_field("Server").unwrap()
|
||||
// // );
|
||||
// }
|
||||
// }
|
||||
@@ -197,12 +197,12 @@ impl VerlocMeasurer {
|
||||
config.packet_timeout,
|
||||
config.connection_timeout,
|
||||
config.delay_between_packets,
|
||||
shutdown_listener.clone(),
|
||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
||||
)),
|
||||
packet_listener: Arc::new(PacketListener::new(
|
||||
config.listening_address,
|
||||
Arc::clone(&identity),
|
||||
shutdown_listener.clone(),
|
||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
||||
)),
|
||||
shutdown_listener,
|
||||
currently_used_api: 0,
|
||||
@@ -241,7 +241,7 @@ impl VerlocMeasurer {
|
||||
return MeasurementOutcome::Done;
|
||||
}
|
||||
|
||||
let mut shutdown_listener = self.shutdown_listener.clone();
|
||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
||||
shutdown_listener.mark_as_success();
|
||||
|
||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||
|
||||
@@ -83,7 +83,7 @@ impl PacketSender {
|
||||
self: Arc<Self>,
|
||||
tested_node: TestedNode,
|
||||
) -> Result<Measurement, RttError> {
|
||||
let mut shutdown_listener = self.shutdown_listener.clone();
|
||||
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
|
||||
shutdown_listener.mark_as_success();
|
||||
|
||||
let mut conn = match tokio::time::timeout(
|
||||
|
||||
@@ -27,4 +27,4 @@ workspace = true
|
||||
|
||||
## wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../wasm-utils"
|
||||
path = "../wasm/utils"
|
||||
|
||||
@@ -9,6 +9,9 @@ pub mod receiver;
|
||||
pub mod tester;
|
||||
|
||||
pub use message::{Empty, TestMessage};
|
||||
pub use nym_sphinx::{
|
||||
chunking::fragment::FragmentIdentifier, params::PacketSize, preparer::PreparedFragment,
|
||||
};
|
||||
pub use tester::NodeTester;
|
||||
|
||||
// it feels wrong to redefine it, but I don't want to import the whole of contract commons just for this one type
|
||||
|
||||
@@ -15,3 +15,4 @@ thiserror = "1.0.37"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7"
|
||||
nym-crypto = { path = "../../crypto", features = ["rand"] }
|
||||
@@ -39,7 +39,7 @@ pub enum RecipientFormattingError {
|
||||
}
|
||||
|
||||
// TODO: this should a different home... somewhere, but where?
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Recipient {
|
||||
client_identity: ClientIdentity,
|
||||
client_encryption_key: ClientEncryptionKey,
|
||||
@@ -226,6 +226,13 @@ impl std::fmt::Display for Recipient {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Recipient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
// use the Display implementation
|
||||
<Self as std::fmt::Display>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Recipient {
|
||||
type Err = RecipientFormattingError;
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::One,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -263,7 +263,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::Two,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -283,7 +283,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::Three,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -303,7 +303,7 @@ mod message_receiver {
|
||||
"EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c",
|
||||
)
|
||||
.unwrap(),
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,6 +78,16 @@ impl OrderedMessageBuffer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks whether the buffer contains enough contiguous regions to read until the specified target sequence.
|
||||
pub fn can_read_until(&self, target: u64) -> bool {
|
||||
for seq in self.next_sequence..=target {
|
||||
if !self.messages.contains_key(&seq) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
|
||||
/// ordered data in the buffer, and `None` if the buffer is empty or has
|
||||
/// gaps in the contained data.
|
||||
@@ -110,7 +120,7 @@ impl OrderedMessageBuffer {
|
||||
);
|
||||
Some(ReadContiguousData {
|
||||
data: contiguous_messages,
|
||||
last_sequence: seq,
|
||||
last_sequence: self.next_sequence - 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ impl Controller {
|
||||
|
||||
if let Some(payload) = active_connection.read_from_buf() {
|
||||
if let Some(closed_at_index) = active_connection.closed_at_index {
|
||||
if payload.last_sequence > closed_at_index {
|
||||
if payload.last_sequence >= closed_at_index {
|
||||
active_connection.is_closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_service_providers_common::interface::{Serializable, ServiceProviderReque
|
||||
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use tap::TapFallible;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -75,7 +76,7 @@ impl RequestDeserializationError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ConnectRequest {
|
||||
// TODO: is connection_id redundant now?
|
||||
pub conn_id: ConnectionId,
|
||||
@@ -83,6 +84,19 @@ pub struct ConnectRequest {
|
||||
pub return_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
impl Debug for ConnectRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConnectRequest")
|
||||
.field("conn_id", &self.conn_id)
|
||||
.field("remote_addr", &self.remote_addr)
|
||||
.field(
|
||||
"return_address",
|
||||
&self.return_address.map(|r| r.to_string()),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SendRequest {
|
||||
pub data: SocketData,
|
||||
|
||||
@@ -64,7 +64,7 @@ pub enum ResponseDeserializationError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Socks5Response {
|
||||
pub protocol_version: Socks5ProtocolVersion,
|
||||
pub content: Socks5ResponseContent,
|
||||
|
||||
+115
-11
@@ -5,6 +5,7 @@ use std::future::Future;
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use futures::{future::pending, FutureExt, SinkExt, StreamExt};
|
||||
use log::{log, Level};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
@@ -23,10 +24,18 @@ pub type SentStatus = Box<dyn Error + Send + Sync>;
|
||||
pub type StatusSender = futures::channel::mpsc::Sender<SentStatus>;
|
||||
pub type StatusReceiver = futures::channel::mpsc::Receiver<SentStatus>;
|
||||
|
||||
fn try_recover_name(name: &Option<String>) -> String {
|
||||
if let Some(name) = name {
|
||||
name.clone()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum TaskError {
|
||||
#[error("Task halted unexpectedly")]
|
||||
UnexpectedHalt,
|
||||
#[error("Task '{}' halted unexpectedly", try_recover_name(.shutdown_name))]
|
||||
UnexpectedHalt { shutdown_name: Option<String> },
|
||||
}
|
||||
|
||||
// TODO: possibly we should create a `Status` trait instead of reusing `Error`
|
||||
@@ -40,6 +49,9 @@ pub enum TaskStatus {
|
||||
/// shutdown. Keeps track of if task stop unexpectedly, such as in a panic.
|
||||
#[derive(Debug)]
|
||||
pub struct TaskManager {
|
||||
// optional name assigned to the task manager that all subscribed task clients will inherit
|
||||
name: Option<String>,
|
||||
|
||||
// These channels have the dual purpose of signalling it's time to shutdown, but also to keep
|
||||
// track of which tasks we are still waiting for.
|
||||
notify_tx: watch::Sender<()>,
|
||||
@@ -72,6 +84,7 @@ impl Default for TaskManager {
|
||||
// there is a listener.
|
||||
let (task_status_tx, task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
Self {
|
||||
name: None,
|
||||
notify_tx,
|
||||
notify_rx: Some(notify_rx),
|
||||
shutdown_timer_secs: DEFAULT_SHUTDOWN_TIMER_SECS,
|
||||
@@ -93,6 +106,12 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn catch_interrupt(mut self) -> Result<(), SentError> {
|
||||
let res = crate::wait_for_signal_and_error(&mut self).await;
|
||||
@@ -107,7 +126,7 @@ impl TaskManager {
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> TaskClient {
|
||||
TaskClient::new(
|
||||
let task_client = TaskClient::new(
|
||||
self.notify_rx
|
||||
.as_ref()
|
||||
.expect("Unable to subscribe to shutdown notifier that is already shutdown")
|
||||
@@ -115,7 +134,13 @@ impl TaskManager {
|
||||
self.task_return_error_tx.clone(),
|
||||
self.task_drop_tx.clone(),
|
||||
self.task_status_tx.clone(),
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(name) = &self.name {
|
||||
task_client.named(format!("{name}-child"))
|
||||
} else {
|
||||
task_client
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
|
||||
@@ -207,8 +232,11 @@ impl TaskManager {
|
||||
|
||||
/// Listen for shutdown notifications, and can send error and status messages back to the
|
||||
/// `TaskManager`
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct TaskClient {
|
||||
// optional name assigned to the shutdown handle
|
||||
name: Option<String>,
|
||||
|
||||
// If a shutdown notification has been registered
|
||||
shutdown: bool,
|
||||
|
||||
@@ -229,7 +257,35 @@ pub struct TaskClient {
|
||||
mode: ClientOperatingMode,
|
||||
}
|
||||
|
||||
impl Clone for TaskClient {
|
||||
fn clone(&self) -> Self {
|
||||
// make sure to not accidentally overflow the stack if we keep cloning the handle
|
||||
let name = if let Some(name) = &self.name {
|
||||
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
|
||||
Some(format!("{name}-child"))
|
||||
} else {
|
||||
Some(Self::OVERFLOW_NAME.to_string())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
TaskClient {
|
||||
name,
|
||||
shutdown: self.shutdown,
|
||||
notify: self.notify.clone(),
|
||||
return_error: self.return_error.clone(),
|
||||
drop_error: self.drop_error.clone(),
|
||||
status_msg: self.status_msg.clone(),
|
||||
mode: self.mode.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskClient {
|
||||
const MAX_NAME_LENGTH: usize = 128;
|
||||
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
|
||||
|
||||
@@ -240,6 +296,7 @@ impl TaskClient {
|
||||
status_msg: StatusSender,
|
||||
) -> TaskClient {
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
notify,
|
||||
return_error,
|
||||
@@ -249,6 +306,41 @@ impl TaskClient {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not convinced about the name...
|
||||
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
let mut child = self.clone();
|
||||
let suffix = child_suffix.into();
|
||||
let child_name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
|
||||
child.name = Some(child_name);
|
||||
child
|
||||
}
|
||||
|
||||
// just a convenience wrapper for including the shutdown name when logging
|
||||
// I really didn't want to create macros for that... because that seemed like an overkill.
|
||||
// but I guess it would have resolved needing to call `format!` for additional msg arguments
|
||||
fn log<S: Into<String>>(&self, level: Level, msg: S) {
|
||||
let msg = msg.into();
|
||||
|
||||
let target = &if let Some(name) = &self.name {
|
||||
format!("TaskClient-{name}")
|
||||
} else {
|
||||
"unnamed-TaskClient".to_string()
|
||||
};
|
||||
|
||||
log!(target: target, level, "{msg}")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn run_future<Fut, T>(&mut self, fut: Fut) -> Option<T>
|
||||
where
|
||||
Fut: Future<Output = T>,
|
||||
@@ -267,6 +359,7 @@ impl TaskClient {
|
||||
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
|
||||
let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
notify: notify_rx,
|
||||
return_error: task_halt_tx,
|
||||
@@ -310,6 +403,7 @@ impl TaskClient {
|
||||
|
||||
pub async fn recv_timeout(&mut self) {
|
||||
if self.mode.is_dummy() {
|
||||
#[cfg_attr(target_arch = "wasm32", allow(clippy::needless_return))]
|
||||
return pending().await;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -336,8 +430,9 @@ impl TaskClient {
|
||||
has_changed
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Polling shutdown failed: {err}");
|
||||
log::error!("Assuming this means we should shutdown...");
|
||||
self.log(Level::Error, format!("Polling shutdown failed: {err}"));
|
||||
self.log(Level::Error, "Assuming this means we should shutdown...");
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -354,9 +449,11 @@ impl TaskClient {
|
||||
if self.mode.is_dummy() {
|
||||
return;
|
||||
}
|
||||
log::trace!("Notifying we stopped: {err}");
|
||||
|
||||
self.log(Level::Trace, format!("Notifying we stopped: {err}"));
|
||||
|
||||
if self.return_error.send(err).is_err() {
|
||||
log::error!("Failed to send back error message");
|
||||
self.log(Level::Error, "failed to send back error message");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,13 +470,20 @@ impl TaskClient {
|
||||
impl Drop for TaskClient {
|
||||
fn drop(&mut self) {
|
||||
if !self.mode.should_signal_on_drop() {
|
||||
self.log(Level::Debug, "the task client is getting dropped");
|
||||
return;
|
||||
} else {
|
||||
self.log(Level::Info, "the task client is getting dropped");
|
||||
}
|
||||
|
||||
if !self.is_shutdown_poll() {
|
||||
log::trace!("Notifying stop on unexpected drop");
|
||||
self.log(Level::Trace, "Notifying stop on unexpected drop");
|
||||
|
||||
// If we can't send, well then there is not much to do
|
||||
self.drop_error
|
||||
.send(Box::new(TaskError::UnexpectedHalt))
|
||||
.send(Box::new(TaskError::UnexpectedHalt {
|
||||
shutdown_name: self.name.clone(),
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ log = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
thiserror = "1.0.37"
|
||||
async-trait = { workspace = true, optional = true }
|
||||
semver = "0.11"
|
||||
|
||||
## internal
|
||||
nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{filter, NetworkAddress};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnet_contract_common::GatewayBond;
|
||||
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
|
||||
@@ -38,7 +38,7 @@ pub struct Node {
|
||||
pub clients_port: u16,
|
||||
pub identity_key: identity::PublicKey,
|
||||
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
|
||||
pub version: String,
|
||||
pub version: NodeVersion,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@@ -83,7 +83,8 @@ impl fmt::Display for Node {
|
||||
|
||||
impl filter::Versioned for Node {
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +115,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
|
||||
clients_port: bond.gateway.clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
|
||||
version: bond.gateway.version.clone(),
|
||||
version: bond.gateway.version.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::filter::VersionFilterable;
|
||||
pub use error::NymTopologyError;
|
||||
use log::warn;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
@@ -25,7 +26,38 @@ pub mod random_route_provider;
|
||||
#[cfg(feature = "provider-trait")]
|
||||
pub mod provider_trait;
|
||||
|
||||
pub use error::NymTopologyError;
|
||||
#[cfg(feature = "provider-trait")]
|
||||
pub use provider_trait::{HardcodedTopologyProvider, TopologyProvider};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum NodeVersion {
|
||||
Explicit(semver::Version),
|
||||
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// this is only implemented for backwards compatibility so we wouldn't need to change everything at once
|
||||
// (also I intentionally implemented `ToString` as opposed to `Display`)
|
||||
impl ToString for NodeVersion {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
NodeVersion::Explicit(semver) => semver.to_string(),
|
||||
NodeVersion::Unknown => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is also for backwards compat.
|
||||
impl<'a> From<&'a str> for NodeVersion {
|
||||
fn from(value: &'a str) -> Self {
|
||||
if let Ok(semver) = value.parse() {
|
||||
NodeVersion::Explicit(semver)
|
||||
} else {
|
||||
NodeVersion::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NetworkAddress {
|
||||
@@ -390,7 +422,7 @@ mod converting_mixes_to_vec {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::One,
|
||||
version: "0.x.0".to_string(),
|
||||
version: "0.2.0".into(),
|
||||
};
|
||||
|
||||
let node2 = mix::Node {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{filter, NetworkAddress};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
pub use nym_mixnet_contract_common::Layer;
|
||||
use nym_mixnet_contract_common::{MixId, MixNodeBond};
|
||||
@@ -39,7 +39,7 @@ pub struct Node {
|
||||
pub identity_key: identity::PublicKey,
|
||||
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
|
||||
pub layer: Layer,
|
||||
pub version: String,
|
||||
pub version: NodeVersion,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@@ -66,7 +66,8 @@ impl Node {
|
||||
|
||||
impl filter::Versioned for Node {
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
|
||||
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?,
|
||||
layer: bond.layer,
|
||||
version: bond.mix_node.version.clone(),
|
||||
version: bond.mix_node.version.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "wasm-utils"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true }
|
||||
js-sys = "^0.3.51"
|
||||
wasm-bindgen = "=0.2.83"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
serde-wasm-bindgen = { version = "0.5.0", optional = true }
|
||||
getrandom = { version="0.2", features=["js"], optional = true }
|
||||
indexed_db_futures = { version = " 0.3.0", optional = true }
|
||||
thiserror = { workspace = true, optional = true }
|
||||
|
||||
nym-store-cipher = { path = "../store-cipher", features = ["json"], optional = true }
|
||||
|
||||
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sleep"]
|
||||
sleep = ["web-sys", "web-sys/Window"]
|
||||
websocket = [
|
||||
"getrandom",
|
||||
"tungstenite",
|
||||
"web-sys",
|
||||
"web-sys/BinaryType",
|
||||
"web-sys/Blob",
|
||||
"web-sys/CloseEvent",
|
||||
"web-sys/ErrorEvent",
|
||||
"web-sys/FileReader",
|
||||
"web-sys/MessageEvent",
|
||||
"web-sys/ProgressEvent",
|
||||
"web-sys/WebSocket",
|
||||
]
|
||||
crypto = [
|
||||
"web-sys",
|
||||
"web-sys/Crypto",
|
||||
"web-sys/CryptoKey",
|
||||
"web-sys/CryptoKeyPair",
|
||||
"web-sys/SubtleCrypto",
|
||||
"web-sys/Window",
|
||||
"web-sys/WorkerGlobalScope",
|
||||
]
|
||||
storage = [
|
||||
"indexed_db_futures",
|
||||
"nym-store-cipher",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"thiserror"
|
||||
]
|
||||
@@ -1,289 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::websocket::state::State;
|
||||
use crate::{console_error, console_log};
|
||||
use futures::{Sink, Stream};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use tungstenite::{Error as WsError, Message as WsMessage}; // use tungstenite Message and Error types for easier compatibility with `ClientHandshake`
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket};
|
||||
|
||||
mod state;
|
||||
|
||||
// Unfortunately this can't be cleanly done with TryFrom/TryInto traits as both are foreign types
|
||||
fn try_message_event_into_ws_message(msg_event: MessageEvent) -> Result<WsMessage, WsError> {
|
||||
match msg_event.data() {
|
||||
buf if buf.is_instance_of::<js_sys::ArrayBuffer>() => {
|
||||
let array = js_sys::Uint8Array::new(&buf);
|
||||
Ok(WsMessage::Binary(array.to_vec()))
|
||||
}
|
||||
blob if blob.is_instance_of::<web_sys::Blob>() => {
|
||||
console_error!("received a blob on the websocket - ignoring it!");
|
||||
// we really don't want to bother dealing with Blobs, because it requires juggling filereaders,
|
||||
// having event handlers to see when they're done, etc.
|
||||
// + we shouldn't even get one [a blob] to begin with
|
||||
// considering that our binary mode is (should be) set to array buffer.
|
||||
Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput)))
|
||||
}
|
||||
text if text.is_string() => match text.as_string() {
|
||||
Some(text) => Ok(WsMessage::Text(text)),
|
||||
None => Err(WsError::Utf8),
|
||||
},
|
||||
// "received a websocket message that is neither a String, ArrayBuffer or a Blob"
|
||||
_ => Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput))),
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: when compiled to wasm32 everything is going to be running on a single thread and so there
|
||||
// is no shared memory right now.
|
||||
//
|
||||
// Eventually it should be made `Send` properly. Wakers should probably be replaced with AtomicWaker
|
||||
// and the item queue put behind an Arc<Mutex<...>>.
|
||||
// It might also be worth looking at what https://crates.io/crates/send_wrapper could provide.
|
||||
// Because I'm not sure Mutex would solve the `Closure` issue. It's the problem for later.
|
||||
//
|
||||
// ************************************
|
||||
// SUPER IMPORTANT TODO: ONCE WASM IN RUST MATURES AND BECOMES MULTI-THREADED THIS MIGHT
|
||||
// LEAD TO RUNTIME MEMORY CORRUPTION!!
|
||||
// ************************************
|
||||
//
|
||||
unsafe impl Send for JSWebsocket {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct JSWebsocket {
|
||||
socket: web_sys::WebSocket,
|
||||
|
||||
message_queue: Rc<RefCell<VecDeque<Result<WsMessage, WsError>>>>,
|
||||
|
||||
/// Waker of a task wanting to read incoming messages.
|
||||
stream_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
/// Waker of a task wanting to write to the sink.
|
||||
sink_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
/// Waker of a sink wanting to close the connection.
|
||||
close_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
// The callback closures. We need to store them as they will invalidate their
|
||||
// corresponding JS callback whenever they are dropped, so if we were to
|
||||
// normally return from `new` then our registered closures will
|
||||
// raise an exception when invoked.
|
||||
_on_open: Closure<dyn FnMut(JsValue)>,
|
||||
_on_error: Closure<dyn FnMut(ErrorEvent)>,
|
||||
_on_close: Closure<dyn FnMut(CloseEvent)>,
|
||||
_on_message: Closure<dyn FnMut(MessageEvent)>,
|
||||
}
|
||||
|
||||
impl JSWebsocket {
|
||||
pub fn new(url: &str) -> Result<Self, JsValue> {
|
||||
let ws = WebSocket::new(url)?;
|
||||
// we don't want to ever have to deal with blobs
|
||||
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
|
||||
let message_queue = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let message_queue_clone = Rc::clone(&message_queue);
|
||||
|
||||
let stream_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let stream_waker_clone = Rc::clone(&stream_waker);
|
||||
let stream_waker_clone2 = Rc::clone(&stream_waker);
|
||||
|
||||
let sink_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let sink_waker_clone = Rc::clone(&sink_waker);
|
||||
let sink_waker_clone2 = Rc::clone(&sink_waker);
|
||||
|
||||
let close_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let close_waker_clone = Rc::clone(&close_waker);
|
||||
|
||||
let on_message = Closure::wrap(Box::new(move |msg_event| {
|
||||
let ws_message = try_message_event_into_ws_message(msg_event);
|
||||
message_queue_clone.borrow_mut().push_back(ws_message);
|
||||
|
||||
// if there is a task waiting for messages - wake the executor!
|
||||
if let Some(waker) = stream_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
let url_clone = url.to_string();
|
||||
let on_open = Closure::wrap(Box::new(move |_| {
|
||||
// in case there was a sink send request made before connection was fully established
|
||||
console_log!("Websocket to {:?} is now open!", url_clone);
|
||||
|
||||
// if there is a task waiting to write messages - wake the executor!
|
||||
if let Some(waker) = sink_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
// no need to wake the stream_waker because we won't have any message to send
|
||||
// immediately anyway. It only makes sense to wake it during on_message (if any)
|
||||
}) as Box<dyn FnMut(JsValue)>);
|
||||
|
||||
let on_error = Closure::wrap(Box::new(move |e: ErrorEvent| {
|
||||
console_error!("Websocket error event: {:?}", e);
|
||||
}) as Box<dyn FnMut(ErrorEvent)>);
|
||||
|
||||
let on_close = Closure::wrap(Box::new(move |e: CloseEvent| {
|
||||
console_log!("Websocket close event: {:?}", e);
|
||||
// something was waiting for the close event!
|
||||
if let Some(waker) = close_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
// TODO: are waking those sufficient to prevent memory leaks?
|
||||
if let Some(waker) = stream_waker_clone2.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
if let Some(waker) = sink_waker_clone2.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
}) as Box<dyn FnMut(CloseEvent)>);
|
||||
|
||||
ws.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||
ws.set_onerror(Some(on_error.as_ref().unchecked_ref()));
|
||||
ws.set_onopen(Some(on_open.as_ref().unchecked_ref()));
|
||||
ws.set_onclose(Some(on_close.as_ref().unchecked_ref()));
|
||||
|
||||
Ok(JSWebsocket {
|
||||
socket: ws,
|
||||
message_queue,
|
||||
stream_waker,
|
||||
sink_waker,
|
||||
close_waker,
|
||||
|
||||
_on_open: on_open,
|
||||
_on_error: on_error,
|
||||
_on_close: on_close,
|
||||
_on_message: on_message,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn close(&mut self, code: Option<u16>) {
|
||||
if let Some(code) = code {
|
||||
self.socket
|
||||
.close_with_code(code)
|
||||
.expect("failed to close the socket!");
|
||||
} else {
|
||||
self.socket.close().expect("failed to close the socket!");
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
self.socket.ready_state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for JSWebsocket {
|
||||
fn drop(&mut self) {
|
||||
match self.state() {
|
||||
State::Closed | State::Closing => {} // no need to do anything here
|
||||
_ => self
|
||||
.socket
|
||||
.close()
|
||||
.expect("failed to close WebSocket during drop!"),
|
||||
}
|
||||
|
||||
self.socket.set_onmessage(None);
|
||||
self.socket.set_onerror(None);
|
||||
self.socket.set_onopen(None);
|
||||
self.socket.set_onclose(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for JSWebsocket {
|
||||
type Item = Result<WsMessage, WsError>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// if there's anything in the internal queue, keep returning that
|
||||
let ws_message = self.message_queue.borrow_mut().pop_front();
|
||||
match ws_message {
|
||||
Some(message) => Poll::Ready(Some(message)),
|
||||
None => {
|
||||
// if connection is closed or closing it means no more useful messages will ever arrive
|
||||
// and hence we should signal this.
|
||||
match self.state() {
|
||||
State::Closing | State::Closed => Poll::Ready(None),
|
||||
State::Open | State::Connecting => {
|
||||
// clone the waker to be able to notify the executor once we get a new message
|
||||
*self.stream_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<WsMessage> for JSWebsocket {
|
||||
type Error = WsError;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self.state() {
|
||||
State::Connecting => {
|
||||
// clone the waker to be able to notify the executor once we get connected
|
||||
*self.sink_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
State::Open => Poll::Ready(Ok(())),
|
||||
State::Closing | State::Closed => Poll::Ready(Err(WsError::AlreadyClosed)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: WsMessage) -> Result<(), Self::Error> {
|
||||
// the only possible errors, per https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
|
||||
|
||||
// are `INVALID_STATE_ERR` which is when connection is not in open state
|
||||
|
||||
// and `SYNTAX_ERR` which is when data is a string that has unpaired surrogates. This one
|
||||
// is essentially impossible to happen in rust (assuming wasm_bindgen has done its jobs
|
||||
// correctly, but even if not, there's nothing we can do ourselves.
|
||||
|
||||
// hence we can map all errors to not open
|
||||
|
||||
match self.state() {
|
||||
State::Open => match item {
|
||||
WsMessage::Binary(data) => self.socket.send_with_u8_array(&data),
|
||||
WsMessage::Text(text) => self.socket.send_with_str(&text),
|
||||
_ => unreachable!("those are not even exposed by the web_sys API"),
|
||||
}
|
||||
.map_err(|_| WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
|
||||
|
||||
State::Closing | State::Closed => Err(WsError::AlreadyClosed),
|
||||
State::Connecting => Err(WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// TODO: can we/should we do anything more here?
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self.state() {
|
||||
State::Open | State::Connecting => {
|
||||
// TODO: do we need to wait for closing event here?
|
||||
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
|
||||
// close inner socket
|
||||
Poll::Ready(self.socket.close().map_err(|_| todo!()))
|
||||
}
|
||||
// if we're already closed, nothing left to do!
|
||||
State::Closed => Poll::Ready(Ok(())),
|
||||
State::Closing => {
|
||||
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
// wait for the close event...
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use web_sys::WebSocket;
|
||||
|
||||
// convenience wrapper for state values provided by [`web_sys::WebSocket`]
|
||||
/// The state values correspond to the `readyState` API of the `WebSocket`.
|
||||
/// See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState) for more details.
|
||||
#[repr(u16)]
|
||||
pub(crate) enum State {
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3,
|
||||
}
|
||||
|
||||
impl From<u16> for State {
|
||||
fn from(state: u16) -> Self {
|
||||
match state {
|
||||
WebSocket::CONNECTING => State::Connecting,
|
||||
WebSocket::OPEN => State::Open,
|
||||
WebSocket::CLOSING => State::Closing,
|
||||
WebSocket::CLOSED => State::Closed,
|
||||
n => panic!("{n} is not a valid WebSocket state!"), // should we panic here or change it into `TryFrom` instead?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "wasm-client-core"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tsify = { workspace = true, features = ["js"] }
|
||||
url = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
||||
nym-client-core = { path = "../../client-core", default-features = false, features = ["wasm"] }
|
||||
nym-config = { path = "../../config" }
|
||||
nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] }
|
||||
nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]}
|
||||
nym-task = { path = "../../task" }
|
||||
nym-topology = { path = "../../topology" }
|
||||
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
|
||||
wasm-utils = { path = "../utils" }
|
||||
wasm-storage = { path = "../storage" }
|
||||
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
+411
-324
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
@@ -6,342 +6,90 @@
|
||||
// another issue due to #[wasm_bindgen] and `Copy` trait
|
||||
#![allow(dropping_copy_types)]
|
||||
|
||||
use nym_client_core::config::{
|
||||
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
|
||||
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
|
||||
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
|
||||
Topology as ConfigTopology, Traffic as ConfigTraffic,
|
||||
};
|
||||
use crate::error::WasmCoreError;
|
||||
use nym_config::helpers::OptionalSet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub(crate) base: BaseClientConfig,
|
||||
pub mod r#override;
|
||||
|
||||
pub use nym_client_core::config::{
|
||||
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
|
||||
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
|
||||
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
|
||||
Topology as ConfigTopology, Traffic as ConfigTraffic,
|
||||
};
|
||||
|
||||
pub fn new_base_client_config(
|
||||
id: String,
|
||||
version: String,
|
||||
nym_api: Option<String>,
|
||||
nyxd: Option<String>,
|
||||
debug: Option<DebugWasm>,
|
||||
) -> Result<BaseClientConfig, WasmCoreError> {
|
||||
let nym_api_url = match nym_api {
|
||||
Some(raw) => Some(
|
||||
raw.parse()
|
||||
.map_err(|source| WasmCoreError::MalformedUrl { raw, source })?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let nyxd_url = match nyxd {
|
||||
Some(raw) => Some(
|
||||
raw.parse()
|
||||
.map_err(|source| WasmCoreError::MalformedUrl { raw, source })?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(BaseClientConfig::new(id, version)
|
||||
.with_optional(
|
||||
BaseClientConfig::with_custom_nym_apis,
|
||||
nym_api_url.map(|u| vec![u]),
|
||||
)
|
||||
.with_optional(
|
||||
BaseClientConfig::with_custom_nyxd,
|
||||
nyxd_url.map(|u| vec![u]),
|
||||
)
|
||||
.with_debug_config(debug.map(Into::into).unwrap_or_default()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Config {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
|
||||
Config {
|
||||
base: BaseClientConfig::new(id, env!("CARGO_PKG_VERSION").to_string())
|
||||
.with_custom_nyxd(vec![validator_server
|
||||
.parse()
|
||||
.expect("provided url was malformed")])
|
||||
.with_debug_config(debug.map(Into::into).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_tester_config<S: Into<String>>(id: S) -> Self {
|
||||
Config {
|
||||
base: BaseClientConfig::new(id.into(), env!("CARGO_PKG_VERSION").to_string())
|
||||
.with_disabled_credentials(true)
|
||||
.with_disabled_cover_traffic(true)
|
||||
.with_disabled_topology_refresh(true),
|
||||
}
|
||||
}
|
||||
pub fn default_debug() -> DebugWasm {
|
||||
ConfigDebug::default().into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = defaultDebug)]
|
||||
pub fn default_debug_obj() -> JsValue {
|
||||
let dbg = default_debug();
|
||||
serde_wasm_bindgen::to_value(&dbg)
|
||||
.expect("our 'DebugWasm' struct should have had a correctly defined serde implementation")
|
||||
}
|
||||
|
||||
/// A client configuration in which no cover traffic will be sent,
|
||||
/// in either the main distribution or the secondary traffic stream.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_packet_delay_ms: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u64,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
pub use_outfox: bool,
|
||||
pub fn no_cover_debug() -> DebugWasm {
|
||||
let mut cfg = ConfigDebug::default();
|
||||
cfg.traffic.disable_main_poisson_packet_distribution = true;
|
||||
cfg.cover_traffic.disable_loop_cover_traffic_stream = true;
|
||||
cfg.into()
|
||||
}
|
||||
|
||||
impl From<TrafficWasm> for ConfigTraffic {
|
||||
fn from(traffic: TrafficWasm) -> Self {
|
||||
let use_extended_packet_size = traffic
|
||||
.use_extended_packet_size
|
||||
.then(|| PacketSize::ExtendedPacket32);
|
||||
|
||||
let packet_type = if traffic.use_outfox {
|
||||
PacketType::Outfox
|
||||
} else {
|
||||
PacketType::Mix
|
||||
};
|
||||
|
||||
ConfigTraffic {
|
||||
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
traffic.message_sending_average_delay_ms,
|
||||
),
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: use_extended_packet_size,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTraffic> for TrafficWasm {
|
||||
fn from(traffic: ConfigTraffic) -> Self {
|
||||
TrafficWasm {
|
||||
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
|
||||
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
|
||||
as u64,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
|
||||
use_outfox: traffic.packet_type == PacketType::Outfox,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CoverTrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u64,
|
||||
|
||||
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
/// Only applicable if `secondary_packet_size` is enabled.
|
||||
pub cover_traffic_primary_size_ratio: f64,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
|
||||
fn from(cover_traffic: CoverTrafficWasm) -> Self {
|
||||
ConfigCoverTraffic {
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
cover_traffic.loop_cover_traffic_average_delay_ms,
|
||||
),
|
||||
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
|
||||
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
|
||||
CoverTrafficWasm {
|
||||
loop_cover_traffic_average_delay_ms: cover_traffic
|
||||
.loop_cover_traffic_average_delay
|
||||
.as_millis() as u64,
|
||||
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GatewayConnectionWasm {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u64,
|
||||
}
|
||||
|
||||
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
|
||||
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
|
||||
ConfigGatewayConnection {
|
||||
gateway_response_timeout: Duration::from_millis(
|
||||
gateway_connection.gateway_response_timeout_ms,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
|
||||
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
|
||||
GatewayConnectionWasm {
|
||||
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
|
||||
as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AcknowledgementsWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_ack_delay_ms: u64,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
pub ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
pub ack_wait_addition_ms: u64,
|
||||
}
|
||||
|
||||
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
|
||||
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
|
||||
ConfigAcknowledgements {
|
||||
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
|
||||
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
|
||||
AcknowledgementsWasm {
|
||||
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TopologyWasm {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
pub topology_refresh_rate_ms: u64,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u64,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
pub disable_refreshing: bool,
|
||||
}
|
||||
|
||||
impl From<TopologyWasm> for ConfigTopology {
|
||||
fn from(topology: TopologyWasm) -> Self {
|
||||
ConfigTopology {
|
||||
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
topology.topology_resolution_timeout_ms,
|
||||
),
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
topology_structure: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTopology> for TopologyWasm {
|
||||
fn from(topology: ConfigTopology) -> Self {
|
||||
TopologyWasm {
|
||||
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
|
||||
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ReplySurbsWasm {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
pub maximum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
pub maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
pub maximum_reply_surb_rerequest_waiting_period_ms: u64,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
/// deciding it's never going to get them and would drop all pending messages
|
||||
pub maximum_reply_surb_drop_waiting_period_ms: u64,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
pub maximum_reply_surb_age_ms: u64,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
pub maximum_reply_key_age_ms: u64,
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
fn from(reply_surbs: ReplySurbsWasm) -> Self {
|
||||
ConfigReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms,
|
||||
),
|
||||
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_surb_drop_waiting_period_ms,
|
||||
),
|
||||
maximum_reply_surb_age: Duration::from_millis(reply_surbs.maximum_reply_surb_age_ms),
|
||||
maximum_reply_key_age: Duration::from_millis(reply_surbs.maximum_reply_key_age_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
fn from(reply_surbs: ConfigReplySurbs) -> Self {
|
||||
ReplySurbsWasm {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u64,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
#[wasm_bindgen(js_name = noCoverDebug)]
|
||||
pub fn no_cover_debug_obj() -> JsValue {
|
||||
let dbg = no_cover_debug();
|
||||
serde_wasm_bindgen::to_value(&dbg)
|
||||
.expect("our 'DebugWasm' struct should have had a correctly defined serde implementation")
|
||||
}
|
||||
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DebugWasm {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
pub traffic: TrafficWasm,
|
||||
@@ -362,6 +110,12 @@ pub struct DebugWasm {
|
||||
pub reply_surbs: ReplySurbsWasm,
|
||||
}
|
||||
|
||||
impl Default for DebugWasm {
|
||||
fn default() -> Self {
|
||||
ConfigDebug::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DebugWasm> for ConfigDebug {
|
||||
fn from(debug: DebugWasm) -> Self {
|
||||
ConfigDebug {
|
||||
@@ -388,7 +142,340 @@ impl From<ConfigDebug> for DebugWasm {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn default_debug() -> DebugWasm {
|
||||
ConfigDebug::default().into()
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_packet_delay_ms: u32,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u32,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
pub use_outfox: bool,
|
||||
}
|
||||
|
||||
impl Default for TrafficWasm {
|
||||
fn default() -> Self {
|
||||
ConfigTraffic::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TrafficWasm> for ConfigTraffic {
|
||||
fn from(traffic: TrafficWasm) -> Self {
|
||||
let use_extended_packet_size = traffic
|
||||
.use_extended_packet_size
|
||||
.then_some(PacketSize::ExtendedPacket32);
|
||||
|
||||
let packet_type = if traffic.use_outfox {
|
||||
PacketType::Outfox
|
||||
} else {
|
||||
PacketType::Mix
|
||||
};
|
||||
|
||||
ConfigTraffic {
|
||||
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms as u64),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
traffic.message_sending_average_delay_ms as u64,
|
||||
),
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: use_extended_packet_size,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTraffic> for TrafficWasm {
|
||||
fn from(traffic: ConfigTraffic) -> Self {
|
||||
TrafficWasm {
|
||||
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u32,
|
||||
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
|
||||
as u32,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
|
||||
use_outfox: traffic.packet_type == PacketType::Outfox,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CoverTrafficWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u32,
|
||||
|
||||
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
/// Only applicable if `secondary_packet_size` is enabled.
|
||||
pub cover_traffic_primary_size_ratio: f64,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl Default for CoverTrafficWasm {
|
||||
fn default() -> Self {
|
||||
ConfigCoverTraffic::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
|
||||
fn from(cover_traffic: CoverTrafficWasm) -> Self {
|
||||
ConfigCoverTraffic {
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
cover_traffic.loop_cover_traffic_average_delay_ms as u64,
|
||||
),
|
||||
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
|
||||
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
|
||||
CoverTrafficWasm {
|
||||
loop_cover_traffic_average_delay_ms: cover_traffic
|
||||
.loop_cover_traffic_average_delay
|
||||
.as_millis() as u32,
|
||||
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GatewayConnectionWasm {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for GatewayConnectionWasm {
|
||||
fn default() -> Self {
|
||||
ConfigGatewayConnection::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
|
||||
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
|
||||
ConfigGatewayConnection {
|
||||
gateway_response_timeout: Duration::from_millis(
|
||||
gateway_connection.gateway_response_timeout_ms as u64,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
|
||||
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
|
||||
GatewayConnectionWasm {
|
||||
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
|
||||
as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AcknowledgementsWasm {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_ack_delay_ms: u32,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
pub ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
pub ack_wait_addition_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for AcknowledgementsWasm {
|
||||
fn default() -> Self {
|
||||
ConfigAcknowledgements::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
|
||||
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
|
||||
ConfigAcknowledgements {
|
||||
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms as u64),
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms as u64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
|
||||
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
|
||||
AcknowledgementsWasm {
|
||||
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u32,
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TopologyWasm {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
pub topology_refresh_rate_ms: u32,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u32,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
pub disable_refreshing: bool,
|
||||
}
|
||||
|
||||
impl Default for TopologyWasm {
|
||||
fn default() -> Self {
|
||||
ConfigTopology::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TopologyWasm> for ConfigTopology {
|
||||
fn from(topology: TopologyWasm) -> Self {
|
||||
ConfigTopology {
|
||||
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms as u64),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
topology.topology_resolution_timeout_ms as u64,
|
||||
),
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
topology_structure: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTopology> for TopologyWasm {
|
||||
fn from(topology: ConfigTopology) -> Self {
|
||||
TopologyWasm {
|
||||
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u32,
|
||||
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u32,
|
||||
disable_refreshing: topology.disable_refreshing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ReplySurbsWasm {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
pub maximum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
pub maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
pub maximum_reply_surb_rerequest_waiting_period_ms: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
/// deciding it's never going to get them and would drop all pending messages
|
||||
pub maximum_reply_surb_drop_waiting_period_ms: u32,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
pub maximum_reply_surb_age_ms: u32,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
pub maximum_reply_key_age_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbsWasm {
|
||||
fn default() -> Self {
|
||||
ConfigReplySurbs::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
fn from(reply_surbs: ReplySurbsWasm) -> Self {
|
||||
ConfigReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms as u64,
|
||||
),
|
||||
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_surb_drop_waiting_period_ms as u64,
|
||||
),
|
||||
maximum_reply_surb_age: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_surb_age_ms as u64,
|
||||
),
|
||||
maximum_reply_key_age: Duration::from_millis(
|
||||
reply_surbs.maximum_reply_key_age_ms as u64,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
fn from(reply_surbs: ConfigReplySurbs) -> Self {
|
||||
ReplySurbsWasm {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period
|
||||
.as_millis() as u32,
|
||||
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period
|
||||
.as_millis() as u32,
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, GatewayConnectionWasm, ReplySurbsWasm,
|
||||
TopologyWasm, TrafficWasm,
|
||||
};
|
||||
use crate::config::ConfigDebug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tsify::Tsify;
|
||||
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DebugWasmOverride {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
#[tsify(optional)]
|
||||
pub traffic: Option<TrafficWasmOverride>,
|
||||
|
||||
/// Defines all configuration options related to cover traffic stream(s).
|
||||
#[tsify(optional)]
|
||||
pub cover_traffic: Option<CoverTrafficWasmOverride>,
|
||||
|
||||
/// Defines all configuration options related to the gateway connection.
|
||||
#[tsify(optional)]
|
||||
pub gateway_connection: Option<GatewayConnectionWasmOverride>,
|
||||
|
||||
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
#[tsify(optional)]
|
||||
pub acknowledgements: Option<AcknowledgementsWasmOverride>,
|
||||
|
||||
/// Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
#[tsify(optional)]
|
||||
pub topology: Option<TopologyWasmOverride>,
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
#[tsify(optional)]
|
||||
pub reply_surbs: Option<ReplySurbsWasmOverride>,
|
||||
}
|
||||
|
||||
impl From<DebugWasmOverride> for DebugWasm {
|
||||
fn from(value: DebugWasmOverride) -> Self {
|
||||
DebugWasm {
|
||||
traffic: value.traffic.map(Into::into).unwrap_or_default(),
|
||||
cover_traffic: value.cover_traffic.map(Into::into).unwrap_or_default(),
|
||||
gateway_connection: value.gateway_connection.map(Into::into).unwrap_or_default(),
|
||||
acknowledgements: value.acknowledgements.map(Into::into).unwrap_or_default(),
|
||||
topology: value.topology.map(Into::into).unwrap_or_default(),
|
||||
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DebugWasmOverride> for ConfigDebug {
|
||||
fn from(value: DebugWasmOverride) -> Self {
|
||||
let debug_wasm: DebugWasm = value.into();
|
||||
debug_wasm.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TrafficWasmOverride {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[tsify(optional)]
|
||||
pub average_packet_delay_ms: Option<u32>,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[tsify(optional)]
|
||||
pub message_sending_average_delay_ms: Option<u32>,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
#[tsify(optional)]
|
||||
pub disable_main_poisson_packet_distribution: Option<bool>,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
#[tsify(optional)]
|
||||
pub use_extended_packet_size: Option<bool>,
|
||||
|
||||
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
#[tsify(optional)]
|
||||
pub use_outfox: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<TrafficWasmOverride> for TrafficWasm {
|
||||
fn from(value: TrafficWasmOverride) -> Self {
|
||||
let def = TrafficWasm::default();
|
||||
|
||||
TrafficWasm {
|
||||
average_packet_delay_ms: value
|
||||
.average_packet_delay_ms
|
||||
.unwrap_or(def.average_packet_delay_ms),
|
||||
message_sending_average_delay_ms: value
|
||||
.message_sending_average_delay_ms
|
||||
.unwrap_or(def.message_sending_average_delay_ms),
|
||||
disable_main_poisson_packet_distribution: value
|
||||
.disable_main_poisson_packet_distribution
|
||||
.unwrap_or(def.disable_main_poisson_packet_distribution),
|
||||
use_extended_packet_size: value
|
||||
.use_extended_packet_size
|
||||
.unwrap_or(def.use_extended_packet_size),
|
||||
use_outfox: value.use_outfox.unwrap_or(def.use_outfox),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CoverTrafficWasmOverride {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
#[tsify(optional)]
|
||||
pub loop_cover_traffic_average_delay_ms: Option<u32>,
|
||||
|
||||
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
/// Only applicable if `secondary_packet_size` is enabled.
|
||||
#[tsify(optional)]
|
||||
pub cover_traffic_primary_size_ratio: Option<f64>,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
#[tsify(optional)]
|
||||
pub disable_loop_cover_traffic_stream: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<CoverTrafficWasmOverride> for CoverTrafficWasm {
|
||||
fn from(value: CoverTrafficWasmOverride) -> Self {
|
||||
let def = CoverTrafficWasm::default();
|
||||
|
||||
CoverTrafficWasm {
|
||||
loop_cover_traffic_average_delay_ms: value
|
||||
.loop_cover_traffic_average_delay_ms
|
||||
.unwrap_or(def.loop_cover_traffic_average_delay_ms),
|
||||
cover_traffic_primary_size_ratio: value
|
||||
.cover_traffic_primary_size_ratio
|
||||
.unwrap_or(def.cover_traffic_primary_size_ratio),
|
||||
disable_loop_cover_traffic_stream: value
|
||||
.disable_loop_cover_traffic_stream
|
||||
.unwrap_or(def.disable_loop_cover_traffic_stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayConnectionWasmOverride {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
#[tsify(optional)]
|
||||
pub gateway_response_timeout_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl From<GatewayConnectionWasmOverride> for GatewayConnectionWasm {
|
||||
fn from(value: GatewayConnectionWasmOverride) -> Self {
|
||||
let def = GatewayConnectionWasm::default();
|
||||
|
||||
GatewayConnectionWasm {
|
||||
gateway_response_timeout_ms: value
|
||||
.gateway_response_timeout_ms
|
||||
.unwrap_or(def.gateway_response_timeout_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AcknowledgementsWasmOverride {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[tsify(optional)]
|
||||
pub average_ack_delay_ms: Option<u32>,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
#[tsify(optional)]
|
||||
pub ack_wait_multiplier: Option<f64>,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
#[tsify(optional)]
|
||||
pub ack_wait_addition_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl From<AcknowledgementsWasmOverride> for AcknowledgementsWasm {
|
||||
fn from(value: AcknowledgementsWasmOverride) -> Self {
|
||||
let def = AcknowledgementsWasm::default();
|
||||
|
||||
AcknowledgementsWasm {
|
||||
average_ack_delay_ms: value
|
||||
.average_ack_delay_ms
|
||||
.unwrap_or(def.average_ack_delay_ms),
|
||||
ack_wait_multiplier: value.ack_wait_multiplier.unwrap_or(def.ack_wait_multiplier),
|
||||
ack_wait_addition_ms: value
|
||||
.ack_wait_addition_ms
|
||||
.unwrap_or(def.ack_wait_addition_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TopologyWasmOverride {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
#[tsify(optional)]
|
||||
pub topology_refresh_rate_ms: Option<u32>,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
#[tsify(optional)]
|
||||
pub topology_resolution_timeout_ms: Option<u32>,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
#[tsify(optional)]
|
||||
pub disable_refreshing: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<TopologyWasmOverride> for TopologyWasm {
|
||||
fn from(value: TopologyWasmOverride) -> Self {
|
||||
let def = TopologyWasm::default();
|
||||
|
||||
TopologyWasm {
|
||||
topology_refresh_rate_ms: value
|
||||
.topology_refresh_rate_ms
|
||||
.unwrap_or(def.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout_ms: value
|
||||
.topology_resolution_timeout_ms
|
||||
.unwrap_or(def.topology_resolution_timeout_ms),
|
||||
disable_refreshing: value.disable_refreshing.unwrap_or(def.disable_refreshing),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReplySurbsWasmOverride {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
#[tsify(optional)]
|
||||
pub minimum_reply_surb_storage_threshold: Option<usize>,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_surb_storage_threshold: Option<usize>,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
#[tsify(optional)]
|
||||
pub minimum_reply_surb_request_size: Option<u32>,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_surb_request_size: Option<u32>,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
#[tsify(optional)]
|
||||
pub maximum_allowed_reply_surb_request_size: Option<u32>,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_surb_rerequest_waiting_period_ms: Option<u32>,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
/// deciding it's never going to get them and would drop all pending messages
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_surb_drop_waiting_period_ms: Option<u32>,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_surb_age_ms: Option<u32>,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[tsify(optional)]
|
||||
pub maximum_reply_key_age_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
fn from(value: ReplySurbsWasmOverride) -> Self {
|
||||
let def = ReplySurbsWasm::default();
|
||||
|
||||
ReplySurbsWasm {
|
||||
minimum_reply_surb_storage_threshold: value
|
||||
.minimum_reply_surb_storage_threshold
|
||||
.unwrap_or(def.minimum_reply_surb_storage_threshold),
|
||||
maximum_reply_surb_storage_threshold: value
|
||||
.maximum_reply_surb_storage_threshold
|
||||
.unwrap_or(def.maximum_reply_surb_storage_threshold),
|
||||
minimum_reply_surb_request_size: value
|
||||
.minimum_reply_surb_request_size
|
||||
.unwrap_or(def.minimum_reply_surb_request_size),
|
||||
maximum_reply_surb_request_size: value
|
||||
.maximum_reply_surb_request_size
|
||||
.unwrap_or(def.maximum_reply_surb_request_size),
|
||||
maximum_allowed_reply_surb_request_size: value
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
.unwrap_or(def.maximum_allowed_reply_surb_request_size),
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: value
|
||||
.maximum_reply_surb_rerequest_waiting_period_ms
|
||||
.unwrap_or(def.maximum_reply_surb_rerequest_waiting_period_ms),
|
||||
maximum_reply_surb_drop_waiting_period_ms: value
|
||||
.maximum_reply_surb_drop_waiting_period_ms
|
||||
.unwrap_or(def.maximum_reply_surb_drop_waiting_period_ms),
|
||||
maximum_reply_surb_age_ms: value
|
||||
.maximum_reply_surb_age_ms
|
||||
.unwrap_or(def.maximum_reply_surb_age_ms),
|
||||
maximum_reply_key_age_ms: value
|
||||
.maximum_reply_key_age_ms
|
||||
.unwrap_or(def.maximum_reply_key_age_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,22 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::error::ClientStorageError;
|
||||
use crate::storage::wasm_client_traits::WasmClientStorageError;
|
||||
use crate::topology::WasmTopologyError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use nym_sphinx::addressing::clients::RecipientFormattingError;
|
||||
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
|
||||
use nym_topology::NymTopologyError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
use wasm_utils::wasm_error;
|
||||
|
||||
// might as well start using well-defined error enum...
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmClientError {
|
||||
#[error(
|
||||
"A node test is already in progress. Wait for it to finish before starting another one."
|
||||
)]
|
||||
TestInProgress,
|
||||
|
||||
pub enum WasmCoreError {
|
||||
#[error("experienced an issue with internal client components: {source}")]
|
||||
BaseClientError {
|
||||
#[from]
|
||||
@@ -58,12 +50,6 @@ pub enum WasmClientError {
|
||||
source: NymTopologyError,
|
||||
},
|
||||
|
||||
#[error("failed to test the node: {source}")]
|
||||
NodeTestingFailure {
|
||||
#[from]
|
||||
source: NetworkTestingError,
|
||||
},
|
||||
|
||||
#[error("{raw} is not a valid url: {source}")]
|
||||
MalformedUrl {
|
||||
raw: String,
|
||||
@@ -92,9 +78,15 @@ pub enum WasmClientError {
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
StorageError {
|
||||
BaseStorageError {
|
||||
#[from]
|
||||
source: ClientStorageError,
|
||||
source: wasm_storage::error::StorageError,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
ClientStorageError {
|
||||
#[from]
|
||||
source: WasmClientStorageError,
|
||||
},
|
||||
|
||||
#[error("this client has already registered with a gateway: {gateway_config:?}")]
|
||||
@@ -103,20 +95,4 @@ pub enum WasmClientError {
|
||||
},
|
||||
}
|
||||
|
||||
impl WasmClientError {
|
||||
pub fn into_rejected_promise(self) -> Promise {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmClientError> for JsValue {
|
||||
fn from(value: WasmClientError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmClientError> for Promise {
|
||||
fn from(value: WasmClientError) -> Self {
|
||||
Promise::reject(&value.into())
|
||||
}
|
||||
}
|
||||
wasm_error!(WasmCoreError);
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::WasmClientError;
|
||||
use crate::error::WasmCoreError;
|
||||
use crate::storage::wasm_client_traits::WasmClientStorage;
|
||||
use crate::storage::ClientStorage;
|
||||
use crate::topology::WasmNymTopology;
|
||||
use js_sys::Promise;
|
||||
@@ -18,44 +19,44 @@ use rand::thread_rng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::PromisableResult;
|
||||
use wasm_utils::error::PromisableResult;
|
||||
|
||||
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
|
||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||
// with no disk_persistence
|
||||
pub(crate) fn setup_reply_surb_storage_backend(
|
||||
config: config::ReplySurbs,
|
||||
) -> browser_backend::Backend {
|
||||
pub fn setup_reply_surb_storage_backend(config: config::ReplySurbs) -> browser_backend::Backend {
|
||||
browser_backend::Backend::new(
|
||||
config.minimum_reply_surb_storage_threshold,
|
||||
config.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
|
||||
pub fn parse_recipient(recipient: &str) -> Result<Recipient, WasmCoreError> {
|
||||
Recipient::try_from_base58_string(recipient).map_err(|source| {
|
||||
WasmClientError::MalformedRecipient {
|
||||
WasmCoreError::MalformedRecipient {
|
||||
raw: recipient.to_string(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
|
||||
pub fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmCoreError> {
|
||||
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
|
||||
WasmClientError::MalformedSenderTag {
|
||||
WasmCoreError::MalformedSenderTag {
|
||||
raw: tag.to_string(),
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn current_network_topology_async(
|
||||
pub async fn current_network_topology_async(
|
||||
nym_api_url: String,
|
||||
) -> Result<WasmNymTopology, WasmClientError> {
|
||||
) -> Result<WasmNymTopology, WasmCoreError> {
|
||||
let url: Url = match nym_api_url.parse() {
|
||||
Ok(url) => url,
|
||||
Err(source) => {
|
||||
return Err(WasmClientError::MalformedUrl {
|
||||
return Err(WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url,
|
||||
source,
|
||||
})
|
||||
@@ -69,11 +70,13 @@ pub(crate) async fn current_network_topology_async(
|
||||
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[wasm_bindgen(js_name = "currentNetworkTopology")]
|
||||
pub fn current_network_topology(nym_api_url: String) -> Promise {
|
||||
// blame js for that serde conversion
|
||||
future_to_promise(async move {
|
||||
current_network_topology_async(nym_api_url)
|
||||
.await
|
||||
.map(|topology| serde_wasm_bindgen::to_value(&topology).unwrap())
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
@@ -82,7 +85,7 @@ async fn setup_gateway(
|
||||
client_store: &ClientStorage,
|
||||
chosen_gateway: Option<IdentityKey>,
|
||||
gateways: &[gateway::Node],
|
||||
) -> Result<InitialisationResult, WasmClientError> {
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let setup = if client_store.has_full_gateway_info().await? {
|
||||
GatewaySetup::MustLoad
|
||||
} else {
|
||||
@@ -94,21 +97,21 @@ async fn setup_gateway(
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn setup_gateway_from_api(
|
||||
pub async fn setup_gateway_from_api(
|
||||
client_store: &ClientStorage,
|
||||
chosen_gateway: Option<IdentityKey>,
|
||||
nym_apis: &[Url],
|
||||
) -> Result<InitialisationResult, WasmClientError> {
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = current_gateways(&mut rng, nym_apis).await?;
|
||||
setup_gateway(client_store, chosen_gateway, &gateways).await
|
||||
}
|
||||
|
||||
pub(crate) async fn setup_from_topology(
|
||||
pub async fn setup_from_topology(
|
||||
explicit_gateway: Option<IdentityKey>,
|
||||
topology: &NymTopology,
|
||||
client_store: &ClientStorage,
|
||||
) -> Result<InitialisationResult, WasmClientError> {
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let gateways = topology.gateways();
|
||||
setup_gateway(client_store, explicit_gateway, gateways).await
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod config;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod helpers;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod storage;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod topology;
|
||||
|
||||
// re-export types for ease of use
|
||||
pub use nym_bandwidth_controller::BandwidthController;
|
||||
pub use nym_client_core::*;
|
||||
pub use nym_client_core::{
|
||||
client::key_manager::ManagedKeys,
|
||||
error::ClientCoreError,
|
||||
init::{InitialisationDetails, InitialisationResult},
|
||||
};
|
||||
pub use nym_gateway_client::{error::GatewayClientError, GatewayClient};
|
||||
pub use nym_sphinx::{
|
||||
addressing::{clients::Recipient, nodes::NodeIdentity},
|
||||
params::PacketType,
|
||||
receiver::ReconstructedMessage,
|
||||
};
|
||||
pub use nym_task;
|
||||
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
|
||||
pub use nym_validator_client::nym_api::Client as ApiClient;
|
||||
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
|
||||
// TODO: that's a very nasty import path. it should come from contracts instead!
|
||||
pub use nym_validator_client::client::IdentityKey;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
+18
-4
@@ -1,7 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::error::ClientStorageError;
|
||||
use crate::config::BaseClientConfig;
|
||||
use crate::error::WasmCoreError;
|
||||
use crate::helpers::setup_reply_surb_storage_backend;
|
||||
use crate::storage::wasm_client_traits::WasmClientStorage;
|
||||
use crate::storage::ClientStorage;
|
||||
use async_trait::async_trait;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::{
|
||||
@@ -22,6 +25,17 @@ pub struct FullWasmClientStorage {
|
||||
pub(crate) credential_storage: EphemeralCredentialStorage,
|
||||
}
|
||||
|
||||
impl FullWasmClientStorage {
|
||||
// TODO: I dont like that base_config type, it should be something wasm-specific.
|
||||
pub fn new(base_config: &BaseClientConfig, base_storage: ClientStorage) -> Self {
|
||||
FullWasmClientStorage {
|
||||
keys_and_gateway_store: base_storage,
|
||||
reply_storage: setup_reply_surb_storage_backend(base_config.debug.reply_surbs),
|
||||
credential_storage: EphemeralCredentialStorage::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetClientStorage for FullWasmClientStorage {
|
||||
type KeyStore = ClientStorage;
|
||||
type ReplyStore = browser_backend::Backend;
|
||||
@@ -52,7 +66,7 @@ impl MixnetClientStorage for FullWasmClientStorage {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl KeyStore for ClientStorage {
|
||||
type StorageError = ClientStorageError;
|
||||
type StorageError = WasmCoreError;
|
||||
|
||||
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
|
||||
console_log!("attempting to load cryptographic keys...");
|
||||
@@ -86,7 +100,7 @@ impl KeyStore for ClientStorage {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl GatewayDetailsStore for ClientStorage {
|
||||
type StorageError = ClientStorageError;
|
||||
type StorageError = WasmCoreError;
|
||||
|
||||
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
|
||||
self.must_read_gateway_details().await
|
||||
@@ -96,6 +110,6 @@ impl GatewayDetailsStore for ClientStorage {
|
||||
&self,
|
||||
details: &PersistedGatewayDetails,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.store_gateway_details(details).await
|
||||
<Self as WasmClientStorage>::store_gateway_details(self, details).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::WasmCoreError;
|
||||
use crate::storage::wasm_client_traits::{v1, WasmClientStorage};
|
||||
use async_trait::async_trait;
|
||||
use js_sys::{Array, Promise};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_storage::traits::BaseWasmStorage;
|
||||
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_utils::error::PromisableResult;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod core_client_traits;
|
||||
pub mod wasm_client_traits;
|
||||
|
||||
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
|
||||
const STORAGE_VERSION: u32 = 1;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: WasmStorage,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClientStorage {
|
||||
fn db_name(client_id: &str) -> String {
|
||||
format!("{STORAGE_NAME_PREFIX}-{client_id}")
|
||||
}
|
||||
|
||||
pub async fn new_async(
|
||||
client_id: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<ClientStorage, WasmCoreError> {
|
||||
let name = Self::db_name(client_id);
|
||||
|
||||
// make sure the password is zeroized when no longer used, especially if we error out.
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let inner = WasmStorage::new(
|
||||
&name,
|
||||
STORAGE_VERSION,
|
||||
migrate_fn,
|
||||
passphrase.as_ref().map(|p| p.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage { inner, name })
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(client_id: String, passphrase: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, Some(passphrase))
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unencrypted(client_id: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, None)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl BaseWasmStorage for ClientStorage {
|
||||
type StorageError = WasmCoreError;
|
||||
|
||||
async fn exists(db_name: &str) -> Result<bool, Self::StorageError> {
|
||||
Ok(WasmStorage::exists(db_name).await?)
|
||||
}
|
||||
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: JsCast,
|
||||
{
|
||||
Ok(self.inner.read_value(store, key).await?)
|
||||
}
|
||||
|
||||
async fn store_value<T, K>(
|
||||
&self,
|
||||
store: &str,
|
||||
key: K,
|
||||
value: &T,
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: JsCast,
|
||||
{
|
||||
Ok(self.inner.store_value(store, key, value).await?)
|
||||
}
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
{
|
||||
Ok(self.inner.remove_value(store, key).await?)
|
||||
}
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
{
|
||||
Ok(self.inner.has_value(store, key).await?)
|
||||
}
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
{
|
||||
Ok(self.inner.key_count(store, key).await?)
|
||||
}
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
||||
Ok(self.inner.get_all_keys(store).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl WasmClientStorage for ClientStorage {
|
||||
type StorageError = WasmCoreError;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx_acknowledgements::AckKey;
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_storage::traits::BaseWasmStorage;
|
||||
|
||||
// v1 tables
|
||||
pub(crate) mod v1 {
|
||||
// stores
|
||||
pub const KEYS_STORE: &str = "keys";
|
||||
pub const CORE_STORE: &str = "core";
|
||||
|
||||
// keys
|
||||
// pub const CONFIG: &str = "config";
|
||||
pub const GATEWAY_DETAILS: &str = "gateway_details";
|
||||
|
||||
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
|
||||
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
|
||||
|
||||
// TODO: for those we could actually use the subtle crypto storage
|
||||
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
|
||||
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmClientStorageError {
|
||||
#[error("{typ} cryptographic key is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
|
||||
#[error("the prior gateway details are not available in the storage")]
|
||||
GatewayDetailsNotInStorage,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait WasmClientStorage: BaseWasmStorage {
|
||||
type StorageError: Error
|
||||
+ From<<Self as BaseWasmStorage>::StorageError>
|
||||
+ From<WasmClientStorageError>;
|
||||
|
||||
async fn may_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<Option<PersistedGatewayDetails>, <Self as WasmClientStorage>::StorageError> {
|
||||
self.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<PersistedGatewayDetails, <Self as WasmClientStorage>::StorageError> {
|
||||
self.may_read_gateway_details()
|
||||
.await?
|
||||
.ok_or(WasmClientStorageError::GatewayDetailsNotInStorage)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<Option<identity::KeyPair>, <Self as WasmClientStorage>::StorageError> {
|
||||
self.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<Option<encryption::KeyPair>, <Self as WasmClientStorage>::StorageError> {
|
||||
self.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_ack_key(
|
||||
&self,
|
||||
) -> Result<Option<AckKey>, <Self as WasmClientStorage>::StorageError> {
|
||||
self.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_gateway_shared_key(
|
||||
&self,
|
||||
) -> Result<Option<SharedKeys>, <Self as WasmClientStorage>::StorageError> {
|
||||
self.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<identity::KeyPair, <Self as WasmClientStorage>::StorageError> {
|
||||
self.may_read_identity_keypair()
|
||||
.await?
|
||||
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<encryption::KeyPair, <Self as WasmClientStorage>::StorageError> {
|
||||
self.may_read_encryption_keypair()
|
||||
.await?
|
||||
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_ack_key(&self) -> Result<AckKey, <Self as WasmClientStorage>::StorageError> {
|
||||
self.may_read_ack_key()
|
||||
.await?
|
||||
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_ACK_KEY.to_string(),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_gateway_shared_key(
|
||||
&self,
|
||||
) -> Result<SharedKeys, <Self as WasmClientStorage>::StorageError> {
|
||||
self.may_read_gateway_shared_key()
|
||||
.await?
|
||||
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
self.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_encryption_keypair(
|
||||
&self,
|
||||
keypair: &encryption::KeyPair,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
self.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_ack_key(
|
||||
&self,
|
||||
key: &AckKey,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
self.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_ACK_KEY),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_shared_key(
|
||||
&self,
|
||||
key: &SharedKeys,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
self.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_details(
|
||||
&self,
|
||||
gateway_endpoint: &PersistedGatewayDetails,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
self.store_value(
|
||||
v1::CORE_STORE,
|
||||
JsValue::from_str(v1::GATEWAY_DETAILS),
|
||||
gateway_endpoint,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn has_full_gateway_info(
|
||||
&self,
|
||||
) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
|
||||
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
|
||||
let has_details = self.may_read_gateway_details().await?.is_some();
|
||||
|
||||
Ok(has_keys && has_details)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::mix::{Layer, MixnodeConversionError};
|
||||
use nym_topology::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_utils::console_log;
|
||||
use wasm_utils::error::simple_js_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmTopologyError {
|
||||
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
|
||||
InvalidMixLayer { value: u8 },
|
||||
|
||||
#[error(transparent)]
|
||||
GatewayConversion(#[from] GatewayConversionError),
|
||||
|
||||
#[error(transparent)]
|
||||
MixnodeConversion(#[from] MixnodeConversionError),
|
||||
|
||||
#[error("The provided mixnode map was malformed: {msg}")]
|
||||
MalformedMixnodeMap { msg: String },
|
||||
|
||||
#[error("The provided gateway list was malformed: {msg}")]
|
||||
MalformedGatewayList { msg: String },
|
||||
}
|
||||
|
||||
impl From<WasmTopologyError> for JsValue {
|
||||
fn from(value: WasmTopologyError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// serde helper, not intended to be used directly
|
||||
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WasmNymTopology {
|
||||
mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>>,
|
||||
gateways: Vec<WasmGateway>,
|
||||
}
|
||||
|
||||
impl WasmNymTopology {
|
||||
pub fn print(&self) {
|
||||
if !self.mixnodes.is_empty() {
|
||||
console_log!("mixnodes:");
|
||||
for (layer, nodes) in &self.mixnodes {
|
||||
console_log!("\tlayer {layer}:");
|
||||
for node in nodes {
|
||||
// console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
|
||||
console_log!("\t\t{} - {:?}", node.mix_id, node)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console_log!("NO MIXNODES")
|
||||
}
|
||||
|
||||
if !self.gateways.is_empty() {
|
||||
console_log!("gateways:");
|
||||
for gateway in &self.gateways {
|
||||
// console_log!("\t{}", gateway.identity_key)
|
||||
console_log!("\t{:?}", gateway)
|
||||
}
|
||||
} else {
|
||||
console_log!("NO GATEWAYS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmNymTopology> for NymTopology {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmNymTopology) -> Result<Self, Self::Error> {
|
||||
let mut converted_mixes = BTreeMap::new();
|
||||
|
||||
for (layer, nodes) in value.mixnodes {
|
||||
let layer_nodes = nodes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
converted_mixes.insert(layer, layer_nodes);
|
||||
}
|
||||
|
||||
let gateways = value
|
||||
.gateways
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(NymTopology::new(converted_mixes, gateways))
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmNymTopology {
|
||||
pub fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
|
||||
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
|
||||
}
|
||||
|
||||
pub fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
|
||||
self.gateways.iter().any(|g| g.identity_key == gateway_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopology> for WasmNymTopology {
|
||||
fn from(value: NymTopology) -> Self {
|
||||
WasmNymTopology {
|
||||
mixnodes: value
|
||||
.mixes()
|
||||
.iter()
|
||||
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
|
||||
.collect(),
|
||||
gateways: value.gateways().iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WasmMixNode {
|
||||
// this is a `MixId` but due to typescript issue, we're using u32 directly.
|
||||
pub mix_id: u32,
|
||||
pub owner: String,
|
||||
pub host: String,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub mix_port: Option<u16>,
|
||||
pub identity_key: String,
|
||||
pub sphinx_key: String,
|
||||
|
||||
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
|
||||
pub layer: u8,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<WasmMixNode> for mix::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
|
||||
let host = mix::Node::parse_host(&value.host)?;
|
||||
|
||||
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
|
||||
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
|
||||
|
||||
Ok(mix::Node {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
layer: Layer::try_from(value.layer)
|
||||
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mix::Node> for WasmMixNode {
|
||||
fn from(value: &'a mix::Node) -> Self {
|
||||
WasmMixNode {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner.clone(),
|
||||
host: value.host.to_string(),
|
||||
mix_port: Some(value.mix_host.port()),
|
||||
identity_key: value.identity_key.to_base58_string(),
|
||||
sphinx_key: value.sphinx_key.to_base58_string(),
|
||||
layer: value.layer.into(),
|
||||
version: Some(value.version.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WasmGateway {
|
||||
pub owner: String,
|
||||
pub host: String,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub mix_port: Option<u16>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub clients_port: Option<u16>,
|
||||
pub identity_key: String,
|
||||
pub sphinx_key: String,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<WasmGateway> for gateway::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
|
||||
let host = gateway::Node::parse_host(&value.host)?;
|
||||
|
||||
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
|
||||
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
|
||||
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = gateway::Node::extract_mix_host(&host, mix_port)?;
|
||||
|
||||
Ok(gateway::Node {
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a gateway::Node> for WasmGateway {
|
||||
fn from(value: &'a gateway::Node) -> Self {
|
||||
WasmGateway {
|
||||
owner: value.owner.clone(),
|
||||
host: value.host.to_string(),
|
||||
mix_port: Some(value.mix_host.port()),
|
||||
clients_port: Some(value.clients_port),
|
||||
identity_key: value.identity_key.to_base58_string(),
|
||||
sphinx_key: value.sphinx_key.to_base58_string(),
|
||||
version: Some(value.version.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "wasm-storage"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
indexed_db_futures = { version = " 0.3.0"}
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-store-cipher = { path = "../../store-cipher", features = ["json"] }
|
||||
wasm-utils = { path = "../utils", default-features = false }
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::simple_js_error;
|
||||
use indexed_db_futures::web_sys::DomException;
|
||||
use serde_wasm_bindgen::Error;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::error::simple_js_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StorageError {
|
||||
@@ -1,22 +1,23 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::console_log;
|
||||
use crate::storage::cipher_export::StoredExportedStoreCipher;
|
||||
use crate::storage::error::StorageError;
|
||||
use futures::TryFutureExt;
|
||||
use crate::cipher_export::StoredExportedStoreCipher;
|
||||
use crate::error::StorageError;
|
||||
use nym_store_cipher::{
|
||||
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
|
||||
Version,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::future::IntoFuture;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
pub use indexed_db_futures::prelude::*;
|
||||
|
||||
mod cipher_export;
|
||||
pub mod error;
|
||||
pub mod traits;
|
||||
|
||||
pub const CIPHER_INFO_STORE: &str = "_cipher_store";
|
||||
pub const CIPHER_STORE_EXPORT: &str = "cipher_store_export_info";
|
||||
@@ -87,6 +88,16 @@ impl WasmStorage {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete(self) -> Result<(), StorageError> {
|
||||
self.inner.0.delete()?.into_future().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
|
||||
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
|
||||
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
|
||||
let db: IdbDatabase = db_req.into_future().await?;
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::WasmStorage;
|
||||
use async_trait::async_trait;
|
||||
use js_sys::Array;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::error::Error;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait BaseWasmStorage {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn exists(db_name: &str) -> Result<bool, Self::StorageError>;
|
||||
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: wasm_bindgen::JsCast;
|
||||
|
||||
async fn store_value<T, K>(
|
||||
&self,
|
||||
store: &str,
|
||||
key: K,
|
||||
value: &T,
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: wasm_bindgen::JsCast;
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl BaseWasmStorage for WasmStorage {
|
||||
type StorageError = crate::error::StorageError;
|
||||
|
||||
async fn exists(db_name: &str) -> Result<bool, Self::StorageError> {
|
||||
WasmStorage::exists(db_name).await
|
||||
}
|
||||
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: wasm_bindgen::JsCast,
|
||||
{
|
||||
self.read_value(store, key).await
|
||||
}
|
||||
|
||||
async fn store_value<T, K>(
|
||||
&self,
|
||||
store: &str,
|
||||
key: K,
|
||||
value: &T,
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: wasm_bindgen::JsCast,
|
||||
{
|
||||
self.store_value(store, key, value).await
|
||||
}
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
{
|
||||
self.remove_value(store, key).await
|
||||
}
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
{
|
||||
self.has_value(store, key).await
|
||||
}
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
{
|
||||
self.key_count(store, key).await
|
||||
}
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
||||
self.get_all_keys(store).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "wasm-utils"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
getrandom = { workspace = true, features=["js"], optional = true }
|
||||
gloo-utils = { workspace = true }
|
||||
gloo-net = { version = "0.3.1", features = ["websocket"], optional = true }
|
||||
#gloo-net = { path = "../../../../gloo/crates/net", features = ["websocket"], optional = true }
|
||||
|
||||
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.web-sys]
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sleep"]
|
||||
sleep = ["web-sys", "web-sys/Window"]
|
||||
websocket = [
|
||||
"getrandom",
|
||||
"tungstenite",
|
||||
"gloo-net"
|
||||
]
|
||||
crypto = [
|
||||
"web-sys",
|
||||
"web-sys/Crypto",
|
||||
"web-sys/CryptoKey",
|
||||
"web-sys/CryptoKeyPair",
|
||||
"web-sys/SubtleCrypto",
|
||||
"web-sys/Window",
|
||||
"web-sys/WorkerGlobalScope",
|
||||
]
|
||||
@@ -1,58 +1,36 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
pub mod websocket;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
pub mod crypto;
|
||||
|
||||
#[cfg(feature = "storage")]
|
||||
pub mod storage;
|
||||
|
||||
// will cause messages to be written as if console.log("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string()))
|
||||
macro_rules! wasm_error {
|
||||
($struct:ident) => {
|
||||
impl $struct {
|
||||
pub fn into_rejected_promise(self) -> js_sys::Promise {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$struct> for wasm_bindgen::JsValue {
|
||||
fn from(value: $struct) -> Self {
|
||||
$crate::error::simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$struct> for js_sys::Promise {
|
||||
fn from(value: $struct) -> Self {
|
||||
Promise::reject(&value.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.warn("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_warn {
|
||||
($($t:tt)*) => ($crate::warn(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.error("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_error {
|
||||
($($t:tt)*) => ($crate::error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn warn(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn error(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "sleep")]
|
||||
pub async fn sleep(ms: i32) -> Result<(), JsValue> {
|
||||
let promise = js_sys::Promise::new(&mut |yes, _| {
|
||||
let win = web_sys::window().expect("no window available!");
|
||||
win.set_timeout_with_callback_and_timeout_and_arguments_0(&yes, ms)
|
||||
.unwrap();
|
||||
});
|
||||
let js_fut = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
js_fut.await?;
|
||||
Ok(())
|
||||
/// A helper that constructs a `Promise` containing a wrapper JS error.
|
||||
pub fn simple_rejected_promise<S: AsRef<str>>(err_message: S) -> Promise {
|
||||
let err = simple_js_error(err_message);
|
||||
Promise::reject(&err)
|
||||
}
|
||||
|
||||
/// A helper that construct a `JsValue` containing an error with the provided message.
|
||||
@@ -121,7 +99,7 @@ where
|
||||
#[macro_export]
|
||||
macro_rules! check_promise_result {
|
||||
( $x:expr ) => {
|
||||
match $crate::PromisableResultError::map_promise_err($x) {
|
||||
match $crate::error::PromisableResultError::map_promise_err($x) {
|
||||
Ok(r) => r,
|
||||
Err(err) => return js_sys::Promise::reject(&err),
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "websocket")]
|
||||
pub mod websocket;
|
||||
|
||||
#[cfg(feature = "crypto")]
|
||||
pub mod crypto;
|
||||
|
||||
pub mod error;
|
||||
|
||||
// will cause messages to be written as if console.log("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.debug("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_debug {
|
||||
($($t:tt)*) => ($crate::debug(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.info("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_info {
|
||||
($($t:tt)*) => ($crate::info(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.warn("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_warn {
|
||||
($($t:tt)*) => ($crate::warn(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// will cause messages to be written as if console.error("...") was called
|
||||
#[macro_export]
|
||||
macro_rules! console_error {
|
||||
($($t:tt)*) => ($crate::error(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn debug(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn info(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn warn(s: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn error(s: &str);
|
||||
}
|
||||
|
||||
#[cfg(feature = "sleep")]
|
||||
pub async fn sleep(ms: i32) -> Result<(), wasm_bindgen::JsValue> {
|
||||
let promise = js_sys::Promise::new(&mut |yes, _| {
|
||||
let win = web_sys::window().expect("no window available!");
|
||||
win.set_timeout_with_callback_and_timeout_and_arguments_0(&yes, ms)
|
||||
.unwrap();
|
||||
});
|
||||
let js_fut = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
js_fut.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::console_log;
|
||||
use futures::{Sink, Stream};
|
||||
use gloo_net::websocket::futures::WebSocket;
|
||||
use gloo_net::websocket::{Message, WebSocketError};
|
||||
use gloo_utils::errors::JsError;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tungstenite::{Error as WsError, Message as WsMessage}; // use tungstenite Message and Error types for easier compatibility with `ClientHandshake`
|
||||
|
||||
// Unfortunately this can't be cleanly done with TryFrom/TryInto traits as both are foreign types
|
||||
fn into_tungstenite_message(msg: gloo_net::websocket::Message) -> WsMessage {
|
||||
match msg {
|
||||
Message::Text(text) => WsMessage::Text(text),
|
||||
Message::Bytes(bytes) => WsMessage::Binary(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_ws_error(err: WebSocketError) -> WsError {
|
||||
match err {
|
||||
// TODO: are we preserving correct semantics?
|
||||
WebSocketError::ConnectionError => WsError::ConnectionClosed,
|
||||
WebSocketError::ConnectionClose(_event) => WsError::ConnectionClosed,
|
||||
WebSocketError::MessageSendError(err) => {
|
||||
WsError::Io(io::Error::new(io::ErrorKind::Other, err.to_string()))
|
||||
}
|
||||
_ => WsError::Io(io::Error::new(io::ErrorKind::Other, "new websocket error")),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_tungstenite_message(msg: WsMessage) -> Result<gloo_net::websocket::Message, WsError> {
|
||||
match msg {
|
||||
WsMessage::Text(text) => Ok(gloo_net::websocket::Message::Text(text)),
|
||||
WsMessage::Binary(bytes) => Ok(gloo_net::websocket::Message::Bytes(bytes)),
|
||||
_ => Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput))),
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: when compiled to wasm32 everything is going to be running on a single thread and so there
|
||||
// is no shared memory right now.
|
||||
//
|
||||
// ************************************
|
||||
// SUPER IMPORTANT TODO: ONCE WASM IN RUST MATURES AND BECOMES MULTI-THREADED THIS MIGHT
|
||||
// LEAD TO RUNTIME MEMORY CORRUPTION!!
|
||||
// ************************************
|
||||
//
|
||||
// note: https://github.com/rustwasm/gloo/issues/109
|
||||
unsafe impl Send for JSWebsocket {}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct JSWebsocket {
|
||||
inner: WebSocket,
|
||||
}
|
||||
|
||||
impl JSWebsocket {
|
||||
pub fn new(url: &str) -> Result<Self, JsError> {
|
||||
let inner = WebSocket::open(url)?;
|
||||
console_log!("Websocket to {:?} is now open!", url);
|
||||
Ok(JSWebsocket { inner })
|
||||
}
|
||||
|
||||
pub async fn close(self, code: Option<u16>, reason: Option<&str>) -> Result<(), JsError> {
|
||||
self.inner.close(code, reason)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for JSWebsocket {
|
||||
type Item = Result<WsMessage, WsError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Pin::new(&mut self.inner).poll_next(cx).map(|maybe_item| {
|
||||
maybe_item.map(|item| item.map(into_tungstenite_message).map_err(map_ws_error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<WsMessage> for JSWebsocket {
|
||||
type Error = WsError;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(&mut self.inner)
|
||||
.poll_ready(cx)
|
||||
.map(|ready_res| ready_res.map_err(map_ws_error))
|
||||
}
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: WsMessage) -> Result<(), Self::Error> {
|
||||
let item = try_from_tungstenite_message(item)?;
|
||||
|
||||
Pin::new(&mut self.inner)
|
||||
.start_send(item)
|
||||
.map_err(map_ws_error)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(&mut self.inner)
|
||||
.poll_flush(cx)
|
||||
.map(|flush_res| flush_res.map_err(map_ws_error))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(&mut self.inner)
|
||||
.poll_close(cx)
|
||||
.map(|close_res| close_res.map_err(map_ws_error))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Summary
|
||||
|
||||
[Introduction](introduction.md)
|
||||
- [Introduction](introduction.md)
|
||||
|
||||
# Infrastructure
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
# Integrations
|
||||
|
||||
- [Integration Options](integrations/integration-options.md)
|
||||
- [Integration FAQ](integrations/faq.md)
|
||||
- [Mixnet Integration](integrations/mixnet-integration.md)
|
||||
- [Payment Integration](integrations/payment-integration.md)
|
||||
|
||||
@@ -51,12 +50,17 @@
|
||||
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
|
||||
|
||||
|
||||
# FAQ
|
||||
|
||||
- [General](faq/general-faq.md)
|
||||
- [Integrations](faq/integrations-faq.md)
|
||||
- [Rewards & Token](faq/rewards-faq.md)
|
||||
|
||||
# Community Resources
|
||||
|
||||
- [Nym DevRel AMAs](community-resources/ama.md)
|
||||
- [Community Applications and Guides](community-resources/community-applications-and-guides.md)
|
||||
- [Change Service Grantee Information](info-request.md)
|
||||
- [Rewards FAQ](community-resources/rewards-faq.md)
|
||||
|
||||
---
|
||||
# Misc.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Nym General FAQ
|
||||
|
||||
> If you have questions which are not answered below, please share them at our [DevRel AMAs](../community-resources/ama.md) or seek support in [Nym's community](https://nymtech.net/community)
|
||||
|
||||
### What is Nym?
|
||||
|
||||
Nym is a privacy platform that secures user data and protects against surveillance at the network level.
|
||||
|
||||
The platform does so by leveraging different technological components:
|
||||
|
||||
- **Nym Mixnet**, a type of overlay network that makes both content and metadata of transactions private through mixing, network-level obfuscation and incentivisation (using Sphinx);
|
||||
- A blockchain called **Nyx**, our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of NYM, as well as smart contracts, in order to create a robust, decentralized, and secure environment incentives for the Mixnet;
|
||||
- **Coconut**, a zero-knowledge signature scheme, that creates an application-level private access control layer to power Zk-Nyms;
|
||||
- A utility token **NYM**, to pay for usage, measure reputation and serve as rewards for the privacy infrastructure.
|
||||
|
||||
Simply put, the [Nym network ("Nym")](https://www.feat-nym-update-nym-web.websites.dev.nymte.ch/nym-whitepaper.pdf) is a decentralized and incentivized infrastructure to provision privacy to a broad range of message-based applications and services. Think of it as a [*"Layer 0" privacy infrastructure*](https://blog.nymtech.net/nym-layer-0-privacy-infrastructure-for-the-whole-internet-e53238f9b8e7) for the entire internet.
|
||||
|
||||
**Related articles:**
|
||||
- [*Nym is not a blockchain, but is powered by one*](https://blog.nymtech.net/nym-is-not-a-blockchain-but-it-is-powered-by-one-4bb16ef16587)
|
||||
- [*Nym tokens*](https://blog.nymtech.net/nym-tokens-where-do-they-live-and-how-are-they-distributed-cross-chain-8d134bf9c41f)
|
||||
- [*A breakdown of the smart contracts that run the Nym token economics*](https://blog.nymtech.net/a-breakdown-of-the-smart-contracts-that-run-the-nym-token-economics-3a61b4139f95)
|
||||
- [*Zk-Nyms are here*](https://blog.nymtech.net/a-breakdown-of-the-smart-contracts-that-run-the-nym-token-economics-3a61b4139f95)
|
||||
- [*Sphinx: the anonymous data format that powers Nym*](https://blog.nymtech.net/sphinx-tl-dr-the-data-packet-that-can-anonymize-bitcoin-and-the-internet-18d152c6e4dc)
|
||||
|
||||
|
||||
### What's the difference between Nym and VPNs?
|
||||
|
||||
Nym is not an onion routing system, it is not a decentralized VPN - it’s much more than that. Nym is a mixnet meant to stop precisely the traffic analysis attacks that Tor and dVPNs are vulnerable to.
|
||||
|
||||
It is an orthogonal design that maintains better privacy and can support anonymity, although usually with a cost in terms of latency.
|
||||
It basically is an infrastructure on which privacy preserving apps can be built, leveraging the Mixnet and Coconut credentials, amongst others.
|
||||
The **Nym mixnet and VPNs differ** because VPNs do not mix nor do they protect metadata from an adversary who may be able to watch the entire network.
|
||||
|
||||
**Related articles:**
|
||||
- [*VPNs, Tor, I2P, how does Nym compare? *](https://blog.nymtech.net/vpns-tor-i2p-how-does-nym-compare-8576824617b8)
|
||||
|
||||
|
||||
### What is Nym’s VPN?
|
||||
|
||||
Since Q2 2023 the Nym core team has been working on launching the **first major consumer facing product** that runs on top of the Nym mixnet: a high speed, trustless and decentralized VPN, paid for via the NYM token - facilitating anonymous payments if wished.
|
||||
The product positions itself as a full-network protection service available across all of a user’s devices, **leveraging the Nym Mixnet** and other primitives to offer split tunneling and traffic obfuscation techniques to protect against censorship.
|
||||
|
||||
**Related articles:**
|
||||
- [*What does it take to build the most powerful VPN?*](https://blog.nymtech.net/what-does-it-take-to-build-the-worlds-most-powerful-vpn-d351a76ec4e6)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Rewards FAQ
|
||||
# Rewards FAQ
|
||||
|
||||
On this page you will find important information about participation in community activities which involve NYM token rewards, such as contests, giveaways, and promotions. Before participating in any such activity, make sure to read this page carefully.
|
||||
|
||||
@@ -5,7 +5,7 @@ __Familiarise yourself with Nym's specific terms and jargon through our glossary
|
||||
## A
|
||||
**[Address](https://nymtech.net/docs/stable/integrations/addresses-in-nym/)** - A unique string of characters generated from a client keypair. Used to identify clients in the mixnet.
|
||||
|
||||
**[Ack](https://nymtech.net/docs/stable/architecture/traffic-flow/#acks)** - Short for "acknowledgement". Whenever a hop is completed, the recieving node will send back an 'ack', so that the sending node knows that the packet was recieved.
|
||||
**[Ack](https://nymtech.net/docs/architecture/traffic-flow.html#acks--package-retransmission)** - Short for "acknowledgement". Whenever a hop is completed, the recieving node will send back an 'ack', so that the sending node knows that the packet was recieved.
|
||||
|
||||
**Attacker** - A person or entity who attempts to gain unauthorised access to a computer system, network, or other information resource.
|
||||
|
||||
@@ -46,7 +46,7 @@ __Familiarise yourself with Nym's specific terms and jargon through our glossary
|
||||
## M
|
||||
**[Metadata](https://www.opendatasoft.com/en/blog/what-is-metadata-and-why-is-it-important-data)** - A set of data that describes and gives information about other data such as IP addresses, device types, geolocations etc.
|
||||
|
||||
**[Mixnet](https://nymtech.net/docs/stable/architecture/traffic-flow/)** - A routing protocol which encrypts and mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom.
|
||||
**[Mixnet](https://nymtech.net/docs/architecture/traffic-flow.html#mixnet-traffic-flow-1)** - A routing protocol which encrypts and mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom.
|
||||
|
||||
**[Mix Nodes](https://blog.nymtech.net/nym-mixnodes-deep-dive-d2b91917f097)** - The backbone of the Nym infrastructure, that are organised in a three-layer network referred to as the mixnet that network traffic passes through. It mixes, hides and reorders traffic and are rewarded in NYM tokens for their quality of service.
|
||||
|
||||
@@ -87,7 +87,7 @@ __Familiarise yourself with Nym's specific terms and jargon through our glossary
|
||||
|
||||
**[Service Provider](https://nymtech.net/docs/stable/tutorials/nym-simple-websocket-tutorial/)** - Any type of app that can communicate with the mixnet via a Nym Client.
|
||||
|
||||
**[SURBs](https://nymtech.net/docs/stable/architecture/traffic-flow)** - Stands for 'Single Use Reply Blocks'. They are a type of crytographic construct used to facilitate secure communication between parties and are designed to allow a sender to send a message to a recipient in sucha a way that the message can only be read by the intended recipient, and cannot be read by anyone else.
|
||||
**[SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs)** - Stands for 'Single Use Reply Blocks'. They are a type of crytographic construct used to facilitate secure communication between parties and are designed to allow a sender to send a message to a recipient in sucha a way that the message can only be read by the intended recipient, and cannot be read by anyone else.
|
||||
|
||||
**Sybil** - In the context of computer networks, a Sybil attach is a type of security attack in which an attacker creates multiple fake identities or "sybils" and uses them to manipulate a network thus gaining control of a network by overwhelming it.
|
||||
|
||||
@@ -103,4 +103,4 @@ __Familiarise yourself with Nym's specific terms and jargon through our glossary
|
||||
**Websocket** - A computer communications protocol that provides a full-duplex channel over a single TCP connection. It is designed to be used as a base for real-time, two-way communication between a client and a server over the web.
|
||||
|
||||
## Z
|
||||
**Zero Knowledge Proofs** - Cryptographic techniques that make it possible to prove something to be true without having to reveal the evidence.
|
||||
**Zero Knowledge Proofs** - Cryptographic techniques that make it possible to prove something to be true without having to reveal the evidence.
|
||||
|
||||
@@ -43,4 +43,4 @@ Maybe you would like to concentrate on building a application that uses the mixn
|
||||
Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor:
|
||||
* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page.
|
||||
|
||||
* Take a look at our [Node Setup Guide](https://nymtech.net/docs/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node.
|
||||
* Take a look at our [Node Setup Guide](https://nymtech.net/operators/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node.
|
||||
|
||||
@@ -6,7 +6,7 @@ Nym is the first system we're aware of which provides integrated protection on b
|
||||
|
||||
The diagram and brief explainer texts below give a high level overview of the difference between Nym and other comparable systems.
|
||||
|
||||
> If you want to dig more deeply into the way traffic is packetised and moved through the mixnet, check out the [Mixnet Traffic Flow](https://nymtech.net/docs/stable/architecture/traffic-flow) page of the docs.
|
||||
> If you want to dig more deeply into the way traffic is packetised and moved through the mixnet, check out the [Mixnet Traffic Flow](https://nymtech.net/docs/architecture/traffic-flow.html) page of the docs.
|
||||
|
||||
<img src="../images/nym-vs-animation.gif"/>
|
||||
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
# What is Nym?
|
||||
|
||||
Nym is a privacy platform that secures user data and protects against surveillance. It leverages the [mixnet](./node-types.md), a type of overlay network that makes both content and metadata of transactions private through network-level obfuscation and incentivisation, and [Coconut](https://nymtech.net/docs/coconut.html), a privacy technology that creates an application-level private access control layer. Nym also utilises [Nyx](https://blog.nymtech.net/nym-now-supports-smart-contracts-2186da46bc7f), our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of `NYM`, as well as smart contracts, in order to create a robust, decentralised, and secure environment for the mixnet. In a nutshell, Nym is a solution that provides strong privacy protection for users in the digital world.
|
||||
Nym is a privacy platform that secures user data and protects against surveillance. It leverages the [mixnet](./node-types.md), a type of overlay network that makes both content and metadata of transactions private through network-level obfuscation and incentivisation, and [Coconut](https://nymtech.net/docs/coconut.html), a privacy technology that creates an application-level private access control layer. Nym also utilises [Nyx](https://blog.nymtech.net/nym-now-supports-smart-contracts-2186da46bc7f), our Cosmos SDK blockchain, to allow for us to use payment tokens in the form of `NYM`, as well as smart contracts, in order to create a robust, decentralised, and secure environment for the mixnet. In a nutshell, Nym is a solution that provides strong privacy protection for users in the digital world.
|
||||
|
||||
### An overlay network for network-level traffic privacy
|
||||
Our mixnet design is based on the [Loopix Anonymity System](https://arxiv.org/abs/1703.00536), somewhat modified to provide better quality of service guarantees.
|
||||
> All the technical nuances about Nym platform can come out quite complex at first. Look for any questions in the [FAQ section](../faq/general-faq.md), take part in our [DevRel AMAs](../community-resources/ama.md) or seek support in [Nym's community](https://nymtech.net/community)
|
||||
|
||||
In this brief video, our Head of Research and creator of the Loopix mixnet paper, Ania Piotrowska, delves into the design of the Loopix mixnet in depth at USENix 2017.
|
||||
### An overlay network for network-level traffic privacy
|
||||
Our mixnet design is based on the [Loopix Anonymity System](https://arxiv.org/abs/1703.00536), somewhat modified to provide better quality of service guarantees.
|
||||
|
||||
<iframe width="700" height="400" src="https://www.youtube.com/embed/R-yEqLX_UvI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
In this brief video, our Head of Research and creator of the Loopix mixnet paper, Ania Piotrowska, delves into the design of the Loopix mixnet in depth at USENix 2017.
|
||||
|
||||
The Nym mixnet effectively addresses privacy concerns by utilizing network nodes to mix messages, making them unidentifiable to adversaries. Each packet is layer encrypted, and binary-padded so that it's indistinguishable from all other packets. Incoming packets are "mixed" with all other messages inside the node. That is, the node strips one layer of packet encryption, and adds a small random transmission delay, so that messages are not emitted in the same order as which they arrived.
|
||||
|
||||
Next, the message is sent to another mix node, decrypted and mixed again, then to a third mixnode for further mixing. Finally, the message is delivered to its destination gateway.
|
||||
<iframe width="700" height="400" src="https://www.youtube.com/embed/R-yEqLX_UvI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
As long as there's enough traffic flowing through the nodes, even an adversary who can monitor the entire network cannot trace the packet flow through the system.
|
||||
The Nym mixnet effectively addresses privacy concerns by utilizing network nodes to mix messages, making them unidentifiable to adversaries. Each packet is layer encrypted, and binary-padded so that it's indistinguishable from all other packets. Incoming packets are "mixed" with all other messages inside the node. That is, the node strips one layer of packet encryption, and adds a small random transmission delay, so that messages are not emitted in the same order as which they arrived.
|
||||
|
||||
Privacy Enhanced Applications (PEApps) that need to defend against network-level monitoring can utilize the Nym mixnet to protect against network-level surveillance.
|
||||
Next, the message is sent to another mix node, decrypted and mixed again, then to a third mixnode for further mixing. Finally, the message is delivered to its destination gateway.
|
||||
|
||||
### Revolutionising Privacy: An Incentivized Mixnet, the First of its Kind
|
||||
Nym is the first mixnet to incentivise its node operators via a cryptocurrency: the `NYM` token. The tokenomic design of Nym ensures that nodes are motivated to provide top-notch performance and robust security, as they are financially rewarded for doing so. The video below contains an explanation of Nym's tokenomic design from Nym's Chief Scientist Claudia Diaz:
|
||||
As long as there's enough traffic flowing through the nodes, even an adversary who can monitor the entire network cannot trace the packet flow through the system.
|
||||
|
||||
<iframe width="700" height="400" src="https://www.youtube.com/embed/Ph51njwcCUE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
Privacy Enhanced Applications (PEApps) that need to defend against network-level monitoring can utilize the Nym mixnet to protect against network-level surveillance.
|
||||
|
||||
### ELI5 Understanding of the Implications of Nym
|
||||
Nym is a type of technology that is designed to keep your online activities private from surveillance networks which track users of many online services. As well as protecting the contents of your messages - the data - Nym's design means that metadata - data about the data - is also kept private. This means that the location from which you sent your message, the time you did so, and a lot of information about your network connection is kept private: this is information that can be used to deanonymise users of other privacy systems, even if they employ encrypted messaging!
|
||||
### Revolutionising Privacy: An Incentivized Mixnet, the First of its Kind
|
||||
Nym is the first mixnet to incentivise its node operators via a cryptocurrency: the `NYM` token. The tokenomic design of Nym ensures that nodes are motivated to provide top-notch performance and robust security, as they are financially rewarded for doing so. The video below contains an explanation of Nym's tokenomic design from Nym's Chief Scientist Claudia Diaz:
|
||||
|
||||
Nym works by "mixing" your data with other users' data, making it much more difficult for anyone to single out and track just your information.
|
||||
<iframe width="700" height="400" src="https://www.youtube.com/embed/Ph51njwcCUE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
This system is unique because it provides an incentive for people to participate and help improve the network, making it stronger and more secure for everyone. By breaking down Nym and understanding its implications, you can see why it's an important tool for anyone who values their privacy online.
|
||||
### ELI5 Understanding of the Implications of Nym
|
||||
Nym is a type of technology that is designed to keep your online activities private from surveillance networks which track users of many online services. As well as protecting the contents of your messages - the data - Nym's design means that metadata - data about the data - is also kept private. This means that the location from which you sent your message, the time you did so, and a lot of information about your network connection is kept private: this is information that can be used to deanonymise users of other privacy systems, even if they employ encrypted messaging!
|
||||
|
||||
Nym works by "mixing" your data with other users' data, making it much more difficult for anyone to single out and track just your information.
|
||||
|
||||
This system is unique because it provides an incentive for people to participate and help improve the network, making it stronger and more secure for everyone. By breaking down Nym and understanding its implications, you can see why it's an important tool for anyone who values their privacy online.
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
# Integration FAQ
|
||||
|
||||
On this page, you'll find links and frequently asked questions on how to get started on integrating your project with Nym's Mixnet and its blockchain, Nyx.
|
||||
|
||||
## Links
|
||||
### General Info
|
||||
* [Nym Website](https://nymtech.net/)
|
||||
* [Nym Mixnet Explorer](https://explorer.nymtech.net/)
|
||||
* [Nyx Block Explorer](https://nym.explorers.guru/)
|
||||
|
||||
### Codebase Info
|
||||
* [Nym Platform Monorepo](https://github.com/nymtech/nym/)
|
||||
* [Nym Project](https://github.com/nymtech/)
|
||||
|
||||
### Documentation Info
|
||||
* [Documentation](https://nymtech.net/docs/)
|
||||
* Developer Portal - you are currently viewing the Developer Portal
|
||||
|
||||
## Wallet Installation
|
||||
The Nym wallet can be downloaded [here](https://nymtech.net/download/).
|
||||
|
||||
You can find all the instructions related to setting up your wallet in the [docs](https://nymtech.net/docs/wallet.html), as well as instructions on how to build the wallet if there is not a downloadable version built for your operating system.
|
||||
|
||||
### What are the machine hardware requirements for Nym Wallet?
|
||||
About 16GB of RAM is recommended for the wallet. However you can expect an average memory usage of ~100MB.
|
||||
|
||||
|
||||
## Interacting with the Nyx blockchain
|
||||
### Where can I find information on the blockchain, such as RPC endpoints?
|
||||
You can find most information required for integration in the [Cosmos Chain Registry](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json) and [Keplr Chain Registry](https://github.com/chainapsis/keplr-chain-registry/blob/main/cosmos/nyx.json) repositories.
|
||||
|
||||
### How can I use `JSON-RPC` methods to interact with the Nyx blockchain?
|
||||
There are multiple ways to use `JSON-RPC` methods to interact with the Nyx blockchain. Which method you use will depend on the type of application you are integrating Nyx interactions into.
|
||||
|
||||
1. The standalone `nyxd` binary can be used for CLI wallets, interacting with smart contracts via the CLI, setting up RPC nodes, and even running validators. This is a version of the Cosmos Hub's `gaiad` binary compiled with Nyx chain configuration, and is written in `Go`. Instructions on setting up the `nyxd` binary can be found [here](https://nymtech.net/docs/nyx/interacting-with-chain.html). This is recommended for more complex commands. For full documentation check the [`gaiad documentation`](https://hub.cosmos.network/main/hub-overview/overview.html#).
|
||||
|
||||
2. `CosmJS` is a Typescript library allowing for developers to interact with CosmosSDK blockchains from a Javascript or Typescript project. You can find it on Github [here](https://github.com/cosmos/cosmjs) and an explainer of its functionality [in the Cosmos Developer Portal](https://tutorials.cosmos.network/tutorials/7-cosmjs/1-cosmjs-intro.html). You can find a list of example apps which use CosmJS [here](https://codesandbox.io/examples/package/@cosmjs/stargate).
|
||||
|
||||
3. The `Nym-CLI` tool, a standalone rust binary which can be built and used according to the [docs](https://nymtech.net/docs/tools/nym-cli.html) can be used in much the same way as `nyxd`. It is a bit simpler to use than the `nyxd` binary, but is not recommended for complex queries, and not all commands are currently implemented. A list of Nym CLI commands and example usage can be found [here](https://nymtech.net/docs/tools/nym-cli.html)
|
||||
|
||||
### How do I generate an address/mnemonic for users to interact with?
|
||||
**Nyxd**
|
||||
|
||||
Use the following command, replacing `your_id` with the ID you want to use for your keypair:
|
||||
```
|
||||
./nyxd keys add your_id --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym
|
||||
```
|
||||
|
||||
**Nym-CLI**
|
||||
```
|
||||
./nym-cli account create
|
||||
```
|
||||
|
||||
Both methods will generate a keypair and log the mnemonic in the console.
|
||||
|
||||
**CosmJS**
|
||||
|
||||
You can find example code for keypair generation [here](https://tutorials.cosmos.network/tutorials/7-cosmjs/2-first-steps.html#testnet-preparation).
|
||||
|
||||
### How to get block information like block height, block hash, block time as so on?
|
||||
**Nyxd**
|
||||
|
||||
You would use one of the subcommands returned by this command:
|
||||
```
|
||||
./nyxd query tx --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym
|
||||
```
|
||||
|
||||
**Nym-CLI**
|
||||
```
|
||||
./nym-cli block current-height
|
||||
```
|
||||
|
||||
**CosmJS**
|
||||
|
||||
`CosmJS` documentation can be found [here](https://cosmos.github.io/cosmjs/). We will be working on example code blocks soon.
|
||||
|
||||
### How to get account/address balance to check there is enough coins to withdraw?
|
||||
**Nyxd**
|
||||
```
|
||||
./nyxd query bank balances <address> --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym
|
||||
```
|
||||
|
||||
**Nym-CLI**
|
||||
```
|
||||
./nym-cli account balance
|
||||
```
|
||||
|
||||
**CosmJS**
|
||||
|
||||
`CosmJS` documentation can be found [here](https://cosmos.github.io/cosmjs/). We will be working on example code blocks soon.
|
||||
|
||||
### How do I transfer tokens to another address?
|
||||
**Nyxd**
|
||||
```
|
||||
./nyxd tx bank send [from_key_or_address] [to_address] [amount] --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym
|
||||
```
|
||||
|
||||
**Nym-CLI**
|
||||
```
|
||||
./nym-cli account send TARGET_ADDRESS AMOUNT
|
||||
```
|
||||
**CosmJS**
|
||||
|
||||
`CosmJS` documentation can be found [here](https://cosmos.github.io/cosmjs/). We will be working on example code blocks soon.
|
||||
|
||||
### Does the address support the inclusion of a `memo` or `destinationTag` when doing the transfer?
|
||||
Yes, it is supported.
|
||||
|
||||
### Can I use my Ledger hardware wallet to interact with the Nyx blockchain?
|
||||
Yes. Follow the instructions in the [Ledger support for Nyx documentation](https://nymtech.net/docs/nyx/ledger-live.html).
|
||||
|
||||
### Where can I find network details such as deployed smart contract addresses?
|
||||
In the [`network defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) file.
|
||||
|
||||
## `NYM` Token
|
||||
The token used to reward mixnet infrastructure operators - `NYM` - is one of the native tokens of the Nyx blockchain. The other token is `NYX`.
|
||||
|
||||
`NYM` is used to incentivise the mixnet, whereas `NYX` is used to secure the Nyx blockchain via Validator staking.
|
||||
|
||||
> Integration with Nym's technology stack will most likely involve using `NYM` if you do need to interact with the Nyx blockchain and transfer tokens.
|
||||
|
||||
### I've seen an ERC20 representation of `NYM` on Ethereum - what's this and how do I use it?
|
||||
|
||||
We use the [Gravity Bridge](https://github.com/Gravity-Bridge) blockchain to bridge an ERC20 representation of `NYM` between the Cosmos ecosystem of IBC-enabled chains and Ethereum mainnet. Gravity Bridge is its own IBC-enabled CosmosSDK chain, which interacts with a smart contract deployed on Ethereum mainnet.
|
||||
|
||||
> The ERC20 representation of `NYM` **cannot** be used with the mixnet; only the native Cosmos representation is usable for staking or bonding nodes.
|
||||
|
||||
If you need to transfer tokens across the bridge, we recommend users use Cosmostation's [spacestation.zone](https://spacestation.zone/) dApp with Metamask and Keplr.
|
||||
|
||||
### What is Circulating Supply and how to find out the distribution amount?
|
||||
|
||||
Circulating supply is the total number of available `NYM`. `NYM` is currently present on the IBC-enabled Nyx blockchain, as well as in ERC20 form on Ethereum Mainnet.
|
||||
|
||||
The Validator API endpoints can be found via the [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). The following endpoints can be called to retrieve the correct distribution amount and circulating supply within Nym.
|
||||
|
||||
Using this API endpoint returns information about the circulating supply of Nym tokens:
|
||||
|
||||
```
|
||||
/circulating-supply
|
||||
```
|
||||
Query Response:
|
||||
|
||||
{
|
||||
"total_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "1000000000000000"
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "241105338883248"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
"amount": "390255200928865"
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "368639460187887"
|
||||
}
|
||||
}
|
||||
|
||||
- `total_supply`- The total number of NYM tokens that have been created and can exist, including those that are currently in circulation and those that are reserved for various purposes.
|
||||
|
||||
- `mixmining_reserved`- The number of NYM tokens that are reserved for the mixnet miners who help to power the Nym network.
|
||||
|
||||
- `vesting_tokens`- The number of NYM tokens that are subject to vesting, meaning they are gradually released over time to certain stakeholders such as the team, advisors, and early investors.
|
||||
|
||||
- `circulating_supply`- The number of NYM tokens that are currently in circulation and available to be traded on the open market, which is calculated by subtracting the `mixmining_reserved` and `vesting_tokens` from the `total_supply`.
|
||||
|
||||
Using this API endpoint returns the current value of the total supply of NYM tokens:
|
||||
|
||||
```
|
||||
/circulating-supply/total-supply-value
|
||||
```
|
||||
Query Response:
|
||||
|
||||
1000000000.0
|
||||
|
||||
> The maximum number of `NYM` tokens that can ever be created is 1 billion.
|
||||
|
||||
Using this API endpoint returns the current value of the circulating supply of NYM tokens:
|
||||
|
||||
```
|
||||
/circulating-supply/circulating-supply-value
|
||||
```
|
||||
Query Response:
|
||||
|
||||
368639460.187887
|
||||
|
||||
> This refers to the present quantity of `NYM` tokens that are actively in circulation.
|
||||
|
||||
|
||||
## Sending traffic through the Nym mixnet
|
||||
### Is the mixnet free to use?
|
||||
For the moment then yes, the mixnet is free to use. There are no limits on the amount of traffic that an app can send through the mixnet.
|
||||
|
||||
### Do I need to run my own gateway to send application traffic through the mixnet?
|
||||
No, although we do recommend that apps that wish to integrate look into running some of their own infrastructure such as gateways in order to assure uptime.
|
||||
|
||||
### How can I find out if an application is already supported by network requester services?
|
||||
You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester-setup.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet.
|
||||
@@ -35,7 +35,7 @@ You can find examples of how to utilise this client in the [Quickstart](../quick
|
||||
In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit!
|
||||
|
||||
* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet.
|
||||
* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet.
|
||||
* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/operators/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet.
|
||||
* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page.
|
||||
|
||||
## Example application traffic flow
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps.
|
||||
|
||||
For more in-depth information about nodes, network traffic flows, etc check out the [docs](https://nymtech.net/docs).
|
||||
For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs). If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user