Compare commits

...

63 Commits

Author SHA1 Message Date
Simon Wicky 005b67ef0f squash 2026-02-13 10:57:42 +01:00
benedettadavico 2f98912778 fix nym-connect version 2023-06-20 13:56:38 +02:00
benedettadavico 9fbab5aaad updating versions for release v1.1.22 2023-06-20 10:24:54 +02:00
benedettadavico 2032b3bdae updating versions for release v1.1.22 2023-06-20 09:51:28 +02:00
Jon Häggblad 3748ab77a1 Statically link openssl (#3504)
* Statically link openssl

* Fix wasm client build

* Enable openssl for nyxd-client feature
2023-06-16 11:27:25 +02:00
pierre 9cbb3dfa0e copy change 2023-06-16 11:19:44 +02:00
Pierre Dommerc 4071d30f3c feat(nc-desktop): add error reporting and monitoring setting (#3553) 2023-06-16 11:09:29 +02:00
Tommy Verrall dd33052f0e Merge pull request #3520 from nymtech/bugfix/incorrect-client-versions
bugfix #3434
2023-06-14 17:23:11 +01:00
Jędrzej Stuczyński a76a51e823 fixed config version of binaries depending on socks5 core configs 2023-06-14 17:16:07 +01:00
Jon Häggblad b31d3c003f Cargo.lock 2023-06-14 15:04:37 +02:00
Jon Häggblad 21e11c9221 Merge remote-tracking branch 'origin/master' into release/v1.1.22 2023-06-14 14:55:30 +02:00
Jon Häggblad c9dcde0ee0 getrandom on workspace level 2023-06-14 13:26:09 +02:00
Jon Häggblad 3fcc16090a Update nym-outfox Cargo.toml in prep for publish to crates.io 2023-06-14 13:15:40 +02:00
farbanas 9288f71c5f Merge branch 'master' into develop 2023-06-13 11:16:17 +02:00
farbanas 79f5983c76 updating lock files 2023-06-13 11:04:36 +02:00
farbanas 335453b63c update versions and changelog for release v1.1.21 2023-06-13 10:54:01 +02:00
Jędrzej Stuczyński 4292a55614 explicitly setting up fresh gateway in native socks5 (#3547) 2023-06-12 17:01:05 +01:00
mx f97f0475e9 Merge pull request #3545 from nymtech/feature/docs-1-1-21
version bump for variables
2023-06-12 12:04:16 +00:00
mx 67a945a15f version bump for variables 2023-06-12 13:45:49 +02:00
Tommy Verrall d1a28826d5 Merge pull request #3535 from nymtech/feature/socks5-message-ordering
Feature/socks5 message ordering
2023-06-12 11:47:35 +01:00
Tommy Verrall 8f026ab6c6 Update build-and-upload-binaries-ci.yml
temp use rust version 1.69.0 for ci build uploads, will need to switch and use: cosmwasm/rust-optimizer in the near future
2023-06-12 11:07:22 +01:00
Jędrzej Stuczyński bdcdcf7f8b clippy 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 182e147a86 fixed and updated related units tests 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 687b437ea0 cargo fmt 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f2c5dbb696 deadcode 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński a8bf690c17 tests 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 1328ba35be removed original Ordered sender 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 8b046d4139 split up functions a bit more 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f4cd372808 simplified keeping track of message sequence 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 7228331db6 removed OrderedMessage 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 45f3f3ec01 cleanup 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 5f9e54c83c buggy, but backwards compatible, explicit sequencing 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f8c2f90502 initial work on putting data sequence explicitly inside socks5 request 2023-06-12 10:20:13 +01:00
Jon Häggblad 96e8bdfea4 Reduce number of surbs in nym-nr-query ping (#3540) 2023-06-12 09:53:47 +02:00
Jon Häggblad 0fa1961a04 nym-nr-query CLI tool for query network-requester (#3538)
* Initial version

* Add open_proxy request

* Start adding cli commands

* use commands

* Common response type

* json output

* Tidy

* Remove the All case

* Remove empty file

* Remove todo

* Add ping command

* Ping 4 times by default

* Logic for controlling number of loops

* Some print tweaks
2023-06-12 09:30:02 +02:00
pierre 88002969b7 ci(nyms5-android): add debug apk build 2023-06-09 22:46:04 +02:00
pierre b90dedf3ae build(android): fix repro build wip 2023-06-09 17:11:15 +02:00
pierre 582cb32223 build(android): fix repro build wip 2023-06-09 16:34:50 +02:00
Jędrzej Stuczyński 31b7921c68 bugfix #3434 2023-06-09 15:12:56 +01:00
Jędrzej Stuczyński 42a43a3709 Feature/extract gateway config (#3517)
* wip client core

* hashing shared key in persisted details

* native client using on-disk gateway details

* ibid for socks5

* ibid for NR

* nym sdk

* non-wasm fixes

* wasm

* missed cargo fmt

* fixed nym-connect build

* changed serialization of the key hash to be more human readable

* allowing some dead code

* fixed gateway details deserializtion

* removed needless borrow in wasm client

* removed deadcode

* exhaustive match on GatewaySetup after having loaded the keys
2023-06-09 15:12:29 +01:00
Jędrzej Stuczyński c5ad4006ae simplified construction of 'BaseClient' (#3513) 2023-06-09 15:11:45 +01:00
Tommy Verrall 71fb6a1ba1 Merge pull request #3516 from nymtech/feature/explorer-sp-extra-detail
Explorer - Extra detail for Service Providers
2023-06-09 10:01:59 +01:00
Jon Häggblad 68a37bc692 Merge remote-tracking branch 'origin/release/v1.1.21' into develop 2023-06-09 09:54:53 +02:00
Jon Häggblad 542fd92a46 Allow query network-requestor for open_proxy (#3531)
* wip: playing around

* WIP

* wip

* Add query request

* Create new example

* Restore control_requests example

* Remove ControlRequest::OpenProxy

* Remove Socks5RequestContent::OpenProxy

* Remove bunch of debug logging

* Remove more old leftovers

* Another few lines of leftovers to remove

* Disable logging in example

* Return error if incoming request if future version

* Serialization unit tests

* Network requester in example

* Cargo.lock
2023-06-09 09:54:21 +02:00
pierre 527c71d3df fix(nyms5-android): clean code 2023-06-08 18:45:02 +02:00
pierre 7ee47bdb4c fix(nyms5-android): fix notif channel creation on android prior to 12 2023-06-08 18:44:18 +02:00
pierre 9fa36b6393 add a log on channel creation 2023-06-08 11:06:14 +02:00
pierre 2b40b96aa1 wip 2023-06-08 11:06:14 +02:00
Jędrzej Stuczyński 9a68702d4d Feature/config refactor (#3498)
* revamping mixnode connfig

* wip

* native client config revamping

* wip

* building socks5

* using const for mixnnode config template

* compiling updated gateway

* nym-api

* nym-sdk

* everything compiling once more

but definitely not compatible with CI and older versions (yet)

* creating full directory structure on init

* renamed paths to storage_paths and fixed mixnode template

* mixnode config migration

* gateway config migration

* nym-api config migration

* native client config migration

* socks5 client config migration

* NR config migration

* removed deprecations (that will be resolved in the following PRs) + fixed clippy

* nym-connect clippy

* nym-connect config updates

* outfox fixes

* defined socks5 lib config

* clippy

* fixed wasm client build

* removed explicit packet_type argument when starting base client

it's known implicitly from the previously passed config struct

* Empty commit

* fixed re-using gateway information when client configs are re-initialised

* fixed borrowing id value in nym-connect

* post-rebase fixes

* updated 'old_config' versions

---------

Co-authored-by: Tommy Verrall <tommy@nymtech.net>
2023-06-07 17:06:35 +01:00
Drazen Urch bc5198768e Push traces to jaeger (#3522)
* Push traces to jaeger

* Remove default feature

* Update toolchain
2023-06-07 17:32:26 +02:00
Jon Häggblad 892653cd96 Remove gateway from detailed service provider type 2023-06-07 14:09:13 +02:00
Jon Häggblad a7471ef324 Replace fold with collect 2023-06-07 14:06:12 +02:00
fmtabbara 403141c1f5 add tooltip to sp routing score 2023-06-07 12:22:59 +01:00
fmtabbara d8c82bf6d0 use default sort order 2023-06-07 12:05:32 +01:00
fmtabbara dd86ba36dd use default success ping time - 120 mins 2023-06-07 11:56:44 +01:00
fmtabbara 0320220219 refactor 2023-06-06 22:56:13 +01:00
fmtabbara e32ee2ccf3 add service-type and routing-scores to sps + allow sorting on routing scores 2023-06-06 22:45:09 +01:00
fmtabbara b8ca1762c2 allow sp routing score to be undefined 2023-06-06 22:45:07 +01:00
pierre d1e9fcf03a make routing score optional 2023-06-06 16:44:17 +02:00
pierre 303a774378 add some logs 2023-06-06 15:25:29 +02:00
pierre c9ca71f47b wip 2023-06-06 14:50:25 +02:00
pierre 92faf1e3d5 wip 2023-06-06 14:22:52 +02:00
fmtabbara e509989ac3 wip 2023-06-06 10:04:50 +01:00
239 changed files with 11140 additions and 7026 deletions
+13 -5
View File
@@ -49,7 +49,7 @@ jobs:
"build-tools;$SDK_BUILDTOOLS_VERSION" "build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@1.70.0
- name: Install rust android targets - name: Install rust android targets
run: | run: |
@@ -60,6 +60,7 @@ jobs:
working-directory: sdk/lib/socks5-listener/ working-directory: sdk/lib/socks5-listener/
env: env:
RELEASE: true RELEASE: true
RUSTFLAGS: "-C link-args=-Wl,--hash-style=gnu"
# build for arm64 and x86_64 # build for arm64 and x86_64
run: ./build-android.sh aarch64 x86_64 run: ./build-android.sh aarch64 x86_64
@@ -68,19 +69,24 @@ jobs:
env: env:
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }} ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
# build for arm64 and x86_64 # build for arm64 and x86_64
run: ./gradlew :app:assembleArch64Release run: |
./gradlew :app:assembleArch64Debug
./gradlew :app:assembleArch64Release
- name: Prepare APKs - name: Prepare APKs
run: | run: |
mkdir apk mkdir apk
mv nym-connect/native/android/app/build/outputs/apk/arch64/debug/app-arch64-debug.apk \
apk/nyms5-arch64-debug.apk
mv nym-connect/native/android/app/build/outputs/apk/arch64/release/app-arch64-release-unsigned.apk \ mv nym-connect/native/android/app/build/outputs/apk/arch64/release/app-arch64-release-unsigned.apk \
apk/nyms5-arch64-release.apk apk/nyms5-arch64-release.apk
- name: Upload APKs - name: Upload APKs
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: nyms5-apk-arch64-release name: nyms5-apk-arch64
path: | path: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk apk/nyms5-arch64-release.apk
gh-release: gh-release:
@@ -93,10 +99,12 @@ jobs:
- name: Download binary artifact - name: Download binary artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: nyms5-apk-arch64-release name: nyms5-apk-arch64
path: apk path: apk
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
files: apk/nyms5-arch64-release.apk files: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
+33
View File
@@ -4,6 +4,39 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased] ## [Unreleased]
## [v1.1.22] (2023-06-20)
- CLI tool for querying network-requesters ([#3539])
- Statically link OpenSSL ([#3510])
- NymConnect - add sentry.io reporting ([#3421])
- init command does not change version number in config.toml ([#3336])
- [Bug] Config version does not correspond to binary version ([#3434])
[#3539]: https://github.com/nymtech/nym/issues/3539
[#3510]: https://github.com/nymtech/nym/issues/3510
[#3421]: https://github.com/nymtech/nym/issues/3421
[#3336]: https://github.com/nymtech/nym/issues/3336
[#3434]: https://github.com/nymtech/nym/issues/3434
## [v1.1.21] (2023-06-13)
- mixFetch: Change socks5 `SendRequest` to include OrderedMessage index as a field rather than making it serialized inside the `data` field
([#3534])
- Explorer - add more data columns to the Service Provider section: ([#3474])
- network-requester: support report if they run an open proxy using `ControlRequest` API ([#3461])
- Refactor client configs (London discussion) ([#3444])
- Increase `DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE` to 2000 to improve reliability ([#3433])
- socks5: sender waits for lanes to clear even though the connection is closed ([#3366])
- version bump for variables ([#3545])
[#3534]: https://github.com/nymtech/nym/issues/3534
[#3474]: https://github.com/nymtech/nym/issues/3474
[#3461]: https://github.com/nymtech/nym/issues/3461
[#3444]: https://github.com/nymtech/nym/issues/3444
[#3433]: https://github.com/nymtech/nym/issues/3433
[#3366]: https://github.com/nymtech/nym/issues/3366
[#3545]: https://github.com/nymtech/nym/pull/3545
## [v1.1.20] (2023-06-06) ## [v1.1.20] (2023-06-06)
- Explorer - Fix SP supported apps list ([#3458]) - Explorer - Fix SP supported apps list ([#3458])
Generated
+407 -57
View File
@@ -68,7 +68,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@@ -204,6 +204,17 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "async-channel"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]] [[package]]
name = "async-file-watcher" name = "async-file-watcher"
version = "0.1.0" version = "0.1.0"
@@ -362,9 +373,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
@@ -378,6 +389,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bip32" name = "bip32"
version = "0.3.0" version = "0.3.0"
@@ -561,6 +581,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.79"
@@ -810,6 +836,15 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "console-api" name = "console-api"
version = "0.5.0" version = "0.5.0"
@@ -911,7 +946,7 @@ dependencies = [
"cosmos-sdk-proto", "cosmos-sdk-proto",
"ecdsa", "ecdsa",
"eyre", "eyre",
"getrandom 0.2.9", "getrandom 0.2.10",
"k256", "k256",
"prost 0.10.4", "prost 0.10.4",
"prost-types 0.10.1", "prost-types 0.10.1",
@@ -1229,6 +1264,36 @@ dependencies = [
"rustc_version 0.2.3", "rustc_version 0.2.3",
] ]
[[package]]
name = "curl"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"winapi",
]
[[package]]
name = "curl-sys"
version = "0.4.63+curl-8.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
"winapi",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "3.2.0" version = "3.2.0"
@@ -1451,7 +1516,16 @@ version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys 0.3.7",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys 0.4.1",
] ]
[[package]] [[package]]
@@ -1465,6 +1539,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "doc-comment" name = "doc-comment"
version = "0.3.3" version = "0.3.3"
@@ -1646,7 +1732,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.2.7", "clap 4.2.7",
@@ -1755,7 +1841,7 @@ dependencies = [
"atomic", "atomic",
"pear", "pear",
"serde", "serde",
"toml", "toml 0.5.11",
"uncased", "uncased",
"version_check", "version_check",
] ]
@@ -1920,6 +2006,21 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.28" version = "0.3.28"
@@ -2016,9 +2117,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.9" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@@ -2623,6 +2724,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "integer-encoding"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]] [[package]]
name = "inventory" name = "inventory"
version = "0.1.11" version = "0.1.11"
@@ -2692,6 +2799,31 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "isahc"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
dependencies = [
"async-channel",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
"event-listener",
"futures-lite",
"http",
"log",
"once_cell",
"polling",
"slab",
"sluice",
"tracing",
"tracing-futures",
"url",
"waker-fn",
]
[[package]] [[package]]
name = "isocountry" name = "isocountry"
version = "0.3.2" version = "0.3.2"
@@ -3190,7 +3322,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.21" version = "1.1.23"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3203,7 +3335,7 @@ dependencies = [
"cw-utils", "cw-utils",
"cw3", "cw3",
"cw4", "cw4",
"dirs", "dirs 4.0.0",
"futures", "futures",
"getset", "getset",
"humantime-serde", "humantime-serde",
@@ -3255,6 +3387,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"ts-rs", "ts-rs",
"url", "url",
"zeroize",
] ]
[[package]] [[package]]
@@ -3290,18 +3423,20 @@ dependencies = [
[[package]] [[package]]
name = "nym-bin-common" name = "nym-bin-common"
version = "0.5.0" version = "0.6.0"
dependencies = [ dependencies = [
"atty", "atty",
"clap 4.2.7", "clap 4.2.7",
"clap_complete", "clap_complete",
"clap_complete_fig", "clap_complete_fig",
"log", "log",
"opentelemetry",
"opentelemetry-jaeger",
"pretty_env_logger", "pretty_env_logger",
"semver 0.11.0", "semver 0.11.0",
"serde", "serde",
"serde_json", "serde_json",
"tracing-appender", "tracing-opentelemetry",
"tracing-subscriber", "tracing-subscriber",
"tracing-tree", "tracing-tree",
"vergen", "vergen",
@@ -3324,7 +3459,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.13.1", "base64 0.13.1",
@@ -3382,16 +3517,16 @@ dependencies = [
"tap", "tap",
"thiserror", "thiserror",
"time 0.3.21", "time 0.3.21",
"toml", "toml 0.5.11",
"url", "url",
] ]
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"clap 4.2.7", "clap 4.2.7",
"dirs", "dirs 4.0.0",
"futures", "futures",
"lazy_static", "lazy_static",
"log", "log",
@@ -3424,11 +3559,12 @@ dependencies = [
[[package]] [[package]]
name = "nym-client-core" name = "nym-client-core"
version = "1.1.14" version = "1.1.15"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.21.2",
"dashmap 5.4.0", "dashmap 5.4.0",
"dirs", "dirs 4.0.0",
"futures", "futures",
"gloo-timers", "gloo-timers",
"humantime-serde", "humantime-serde",
@@ -3449,6 +3585,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.6",
"sqlx 0.6.3", "sqlx 0.6.3",
"tap", "tap",
"tempfile", "tempfile",
@@ -3485,7 +3622,7 @@ dependencies = [
"digest 0.9.0", "digest 0.9.0",
"doc-comment", "doc-comment",
"ff 0.11.1", "ff 0.11.1",
"getrandom 0.2.9", "getrandom 0.2.10",
"group 0.11.0", "group 0.11.0",
"itertools", "itertools",
"nym-dkg", "nym-dkg",
@@ -3535,12 +3672,12 @@ dependencies = [
name = "nym-config" name = "nym-config"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cfg-if", "dirs 5.0.1",
"handlebars", "handlebars",
"log", "log",
"nym-network-defaults", "nym-network-defaults",
"serde", "serde",
"toml", "toml 0.7.4",
"url", "url",
] ]
@@ -3564,6 +3701,7 @@ dependencies = [
"log", "log",
"nym-bandwidth-controller", "nym-bandwidth-controller",
"nym-bin-common", "nym-bin-common",
"nym-client-core",
"nym-config", "nym-config",
"nym-credential-storage", "nym-credential-storage",
"nym-credentials", "nym-credentials",
@@ -3659,7 +3797,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3669,7 +3807,7 @@ dependencies = [
"clap 4.2.7", "clap 4.2.7",
"colored", "colored",
"dashmap 4.0.2", "dashmap 4.0.2",
"dirs", "dirs 4.0.0",
"dotenvy", "dotenvy",
"futures", "futures",
"humantime-serde", "humantime-serde",
@@ -3712,7 +3850,7 @@ name = "nym-gateway-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"log", "log",
"nym-bandwidth-controller", "nym-bandwidth-controller",
"nym-coconut-interface", "nym-coconut-interface",
@@ -3810,7 +3948,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.21" version = "1.1.23"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bs58", "bs58",
@@ -3819,7 +3957,7 @@ dependencies = [
"colored", "colored",
"cpu-cycles", "cpu-cycles",
"cupid", "cupid",
"dirs", "dirs 4.0.0",
"futures", "futures",
"humantime-serde", "humantime-serde",
"lazy_static", "lazy_static",
@@ -3848,7 +3986,7 @@ dependencies = [
"sysinfo", "sysinfo",
"tokio", "tokio",
"tokio-util", "tokio-util",
"toml", "toml 0.5.11",
"tracing", "tracing",
"url", "url",
] ]
@@ -3920,13 +4058,14 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"anyhow",
"async-file-watcher", "async-file-watcher",
"async-trait", "async-trait",
"bs58", "bs58",
"clap 4.2.7", "clap 4.2.7",
"dirs", "dirs 4.0.0",
"futures", "futures",
"humantime-serde", "humantime-serde",
"ipnetwork 0.20.0", "ipnetwork 0.20.0",
@@ -3965,9 +4104,9 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-statistics" name = "nym-network-statistics"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"dirs", "dirs 4.0.0",
"log", "log",
"nym-bin-common", "nym-bin-common",
"nym-statistics-common", "nym-statistics-common",
@@ -4011,6 +4150,22 @@ dependencies = [
"wasm-timer", "wasm-timer",
] ]
[[package]]
name = "nym-nr-query"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.2.7",
"log",
"nym-bin-common",
"nym-network-defaults",
"nym-sdk",
"nym-service-providers-common",
"nym-socks5-requests",
"serde",
"tokio",
]
[[package]] [[package]]
name = "nym-ordered-buffer" name = "nym-ordered-buffer"
version = "0.1.0" version = "0.1.0"
@@ -4029,7 +4184,7 @@ dependencies = [
"criterion", "criterion",
"curve25519-dalek", "curve25519-dalek",
"fastrand", "fastrand",
"getrandom 0.2.9", "getrandom 0.2.10",
"log", "log",
"rand 0.7.3", "rand 0.7.3",
"rayon", "rayon",
@@ -4072,7 +4227,7 @@ dependencies = [
"tap", "tap",
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml 0.5.11",
"url", "url",
] ]
@@ -4098,6 +4253,7 @@ dependencies = [
"log", "log",
"nym-bin-common", "nym-bin-common",
"nym-sdk", "nym-sdk",
"nym-socks5-requests",
"nym-sphinx-anonymous-replies", "nym-sphinx-anonymous-replies",
"serde", "serde",
"serde_json", "serde_json",
@@ -4107,7 +4263,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.20" version = "1.1.22"
dependencies = [ dependencies = [
"clap 4.2.7", "clap 4.2.7",
"lazy_static", "lazy_static",
@@ -4139,7 +4295,7 @@ dependencies = [
name = "nym-socks5-client-core" name = "nym-socks5-client-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dirs", "dirs 4.0.0",
"futures", "futures",
"log", "log",
"nym-bandwidth-controller", "nym-bandwidth-controller",
@@ -4180,6 +4336,7 @@ dependencies = [
"openssl", "openssl",
"rand 0.7.3", "rand 0.7.3",
"safer-ffi", "safer-ffi",
"serde",
"tokio", "tokio",
] ]
@@ -4202,10 +4359,13 @@ dependencies = [
name = "nym-socks5-requests" name = "nym-socks5-requests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode",
"log",
"nym-service-providers-common", "nym-service-providers-common",
"nym-sphinx-addressing", "nym-sphinx-addressing",
"serde", "serde",
"serde_json", "serde_json",
"tap",
"thiserror", "thiserror",
] ]
@@ -4377,7 +4537,7 @@ dependencies = [
"aes-gcm", "aes-gcm",
"argon2", "argon2",
"generic-array 0.14.7", "generic-array 0.14.7",
"getrandom 0.2.9", "getrandom 0.2.10",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
@@ -4471,6 +4631,7 @@ dependencies = [
"nym-service-provider-directory-common", "nym-service-provider-directory-common",
"nym-vesting-contract", "nym-vesting-contract",
"nym-vesting-contract-common", "nym-vesting-contract-common",
"openssl",
"prost 0.10.4", "prost 0.10.4",
"reqwest", "reqwest",
"serde", "serde",
@@ -4633,12 +4794,55 @@ dependencies = [
"opentelemetry_sdk", "opentelemetry_sdk",
] ]
[[package]]
name = "opentelemetry-http"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a819b71d6530c4297b49b3cae2939ab3a8cc1b9f382826a1bc29dd0ca3864906"
dependencies = [
"async-trait",
"bytes",
"http",
"isahc",
"opentelemetry_api",
]
[[package]]
name = "opentelemetry-jaeger"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08e028dc9f4f304e9320ce38c80e7cf74067415b1ad5a8750a38bae54a4d450d"
dependencies = [
"async-trait",
"futures",
"futures-executor",
"http",
"isahc",
"once_cell",
"opentelemetry",
"opentelemetry-http",
"opentelemetry-semantic-conventions",
"thiserror",
"thrift",
"tokio",
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e33428e6bf08c6f7fcea4ddb8e358fab0fe48ab877a87c70c6ebe20f673ce5"
dependencies = [
"opentelemetry",
]
[[package]] [[package]]
name = "opentelemetry_api" name = "opentelemetry_api"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2"
dependencies = [ dependencies = [
"fnv",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"indexmap", "indexmap",
@@ -4656,6 +4860,8 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"crossbeam-channel", "crossbeam-channel",
"dashmap 5.4.0",
"fnv",
"futures-channel", "futures-channel",
"futures-executor", "futures-executor",
"futures-util", "futures-util",
@@ -4664,6 +4870,23 @@ dependencies = [
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"thiserror", "thiserror",
"tokio",
"tokio-stream",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
dependencies = [
"num-traits",
] ]
[[package]] [[package]]
@@ -4696,6 +4919,12 @@ dependencies = [
"group 0.11.0", "group 0.11.0",
] ]
[[package]]
name = "parking"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.2" version = "0.11.2"
@@ -4958,6 +5187,22 @@ dependencies = [
"plotters-backend", "plotters-backend",
] ]
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg 1.1.0",
"bitflags",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "poly1305" name = "poly1305"
version = "0.8.0" version = "0.8.0"
@@ -5272,7 +5517,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
] ]
[[package]] [[package]]
@@ -5429,7 +5674,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"redox_syscall 0.2.16", "redox_syscall 0.2.16",
"thiserror", "thiserror",
] ]
@@ -5492,7 +5737,7 @@ version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.2",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@@ -5762,7 +6007,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.2",
] ]
[[package]] [[package]]
@@ -6039,6 +6284,15 @@ dependencies = [
"syn 2.0.16", "syn 2.0.16",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -6180,6 +6434,17 @@ dependencies = [
"autocfg 1.1.0", "autocfg 1.1.0",
] ]
[[package]]
name = "sluice"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
dependencies = [
"async-channel",
"futures-core",
"futures-io",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.10.0"
@@ -6667,7 +6932,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tendermint", "tendermint",
"toml", "toml 0.5.11",
"url", "url",
] ]
@@ -6699,7 +6964,7 @@ dependencies = [
"bytes", "bytes",
"flex-error", "flex-error",
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"http", "http",
"hyper", "hyper",
"hyper-proxy", "hyper-proxy",
@@ -6767,6 +7032,28 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "thrift"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09"
dependencies = [
"byteorder",
"integer-encoding",
"log",
"ordered-float",
"threadpool",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.45" version = "0.1.45"
@@ -6990,6 +7277,40 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.9.2" version = "0.9.2"
@@ -6998,7 +7319,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
"base64 0.21.0", "base64 0.21.2",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -7057,22 +7378,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-appender"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
dependencies = [
"crossbeam-channel",
"time 0.3.21",
"tracing-subscriber",
]
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.24" version = "0.1.24"
@@ -7094,6 +7405,16 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
]
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.1.3" version = "0.1.3"
@@ -7105,6 +7426,20 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-opentelemetry"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00a39dcf9bfc1742fa4d6215253b33a6e474be78275884c216fc2a06267b3600"
dependencies = [
"once_cell",
"opentelemetry",
"tracing",
"tracing-core",
"tracing-log",
"tracing-subscriber",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.17" version = "0.3.17"
@@ -7399,7 +7734,7 @@ version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -7438,6 +7773,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.3" version = "2.3.3"
@@ -7561,7 +7902,7 @@ name = "wasm-utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"indexed_db_futures", "indexed_db_futures",
"js-sys", "js-sys",
"nym-store-cipher", "nym-store-cipher",
@@ -7809,6 +8150,15 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"
+3 -1
View File
@@ -87,6 +87,7 @@ members = [
"nym-api/nym-api-requests", "nym-api/nym-api-requests",
"nym-outfox", "nym-outfox",
"tools/nym-cli", "tools/nym-cli",
"tools/nym-nr-query",
"tools/ts-rs-cli" "tools/ts-rs-cli"
] ]
@@ -112,8 +113,8 @@ edition = "2021"
license = "Apache-2.0" license = "Apache-2.0"
[workspace.dependencies] [workspace.dependencies]
async-trait = "0.1.64"
anyhow = "1.0.71" anyhow = "1.0.71"
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] } bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0" cfg-if = "1.0.0"
cosmwasm-derive = "=1.0.0" cosmwasm-derive = "=1.0.0"
@@ -129,6 +130,7 @@ cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" } cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6" dotenvy = "0.15.6"
generic-array = "0.14.7" generic-array = "0.14.7"
getrandom = "0.2.10"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4" log = "0.4"
once_cell = "1.7.2" once_cell = "1.7.2"
+2
View File
@@ -12,7 +12,9 @@ serde = { workspace = true, features = ["derive"] }
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-client-core = { path = "../../common/client-core" }
nym-config = { path = "../../common/config" } nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" } nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" } nym-credential-storage = { path = "../../common/credential-storage" }
+8 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
mod commands; mod commands;
@@ -9,13 +9,14 @@ use commands::*;
use error::Result; use error::Result;
use log::*; use log::*;
use nym_bin_common::completions::fig_generate; use nym_bin_common::completions::fig_generate;
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR}; use nym_config::DEFAULT_DATA_DIR;
use nym_network_defaults::{setup_env, NymNetworkDetails}; use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit; use std::process::exit;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use nym_bin_common::logging::setup_logging; use nym_bin_common::logging::setup_logging;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_validator_client::nyxd::traits::DkgQueryClient; use nym_validator_client::nyxd::traits::DkgQueryClient;
use nym_validator_client::nyxd::{Coin, CosmWasmClient}; use nym_validator_client::nyxd::{Coin, CosmWasmClient};
use nym_validator_client::Config; use nym_validator_client::Config;
@@ -71,10 +72,11 @@ async fn main() -> Result<()> {
match args.command { match args.command {
Command::Run(r) => { Command::Run(r) => {
let db_path = r // we assume the structure of <home-dir>/data
.client_home_directory let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR);
.join(DATA_DIR) let paths = CommonClientPaths::new_default(data_dir);
.join(CRED_DB_FILE_NAME); let db_path = paths.credentials_database;
let shared_storage = let shared_storage =
nym_credential_storage::initialise_persistent_storage(db_path).await; nym_credential_storage::initialise_persistent_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.20" version = "1.1.22"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+92 -101
View File
@@ -1,99 +1,104 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template; use crate::client::config::persistence::ClientPaths;
use nym_client_core::config::ClientCoreConfigTrait; use crate::client::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet}; use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::io;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
pub use nym_client_core::config::Config as BaseConfig; pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_client_core::config::MISSING_VALUE;
pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig}; pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig};
pub mod old_config_v1_1_13; pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template; mod template;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] const DEFAULT_CLIENTS_DIR: &str = "clients";
#[serde(deny_unknown_fields)]
pub enum SocketType { /// Derive default path to clients's config directory.
WebSocket, /// It should get resolved to `$HOME/.nym/mixnodes/<id>/config`
None, pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_CLIENTS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
} }
impl SocketType { /// Derive default path to client's config file.
pub fn from_string<S: Into<String>>(val: S) -> Self { /// It should get resolved to `$HOME/.nym/clients/<id>/config/config.toml`
let mut upper = val.into(); pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
upper.make_ascii_uppercase(); default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
match upper.as_ref() {
"WEBSOCKET" | "WS" => SocketType::WebSocket,
_ => SocketType::None,
}
}
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
} }
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] /// Derive default path to client's data directory where files, such as keys, are stored.
#[serde(deny_unknown_fields)] /// It should get resolved to `$HOME/.nym/clients/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_CLIENTS_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Config { pub struct Config {
#[serde(flatten)] #[serde(flatten)]
base: BaseConfig<Config>, pub base: BaseClientConfig,
socket: Socket, pub socket: Socket,
// pub paths: CommonClientPathfinder,
pub storage_paths: ClientPaths,
pub logging: LoggingSettings,
} }
impl NymConfig for Config { impl NymConfigTemplate for Config {
fn template() -> &'static str { fn template() -> &'static str {
config_template() CONFIG_TEMPLATE
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.get_nym_root_directory()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(self.base.get_id())
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join(self.base.get_id()).join("data")
}
}
impl ClientCoreConfigTrait for Config {
fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig {
self.base.get_gateway_endpoint()
} }
} }
impl Config { impl Config {
pub fn new<S: Into<String>>(id: S) -> Self { pub fn new<S: AsRef<str>>(id: S) -> Self {
Config { Config {
base: BaseConfig::new(id), base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
storage_paths: ClientPaths::new_default(default_data_directory(id.as_ref())),
logging: Default::default(),
socket: Default::default(), socket: Default::default(),
} }
} }
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.base.client.id)
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn validate(&self) -> bool { pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet) // no other sections have explicit requirements (yet)
self.base.validate() self.base.validate()
@@ -123,39 +128,10 @@ impl Config {
self self
} }
// getters
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
}
pub fn get_base(&self) -> &BaseConfig<Self> {
&self.base
}
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> {
&mut self.base
}
pub fn get_debug_settings(&self) -> &DebugConfig {
self.get_base().get_debug_config()
}
pub fn get_socket_type(&self) -> SocketType {
self.socket.socket_type
}
pub fn get_listening_ip(&self) -> IpAddr {
self.socket.host
}
pub fn get_listening_port(&self) -> u16 {
self.socket.listening_port
}
// poor man's 'builder' method // poor man's 'builder' method
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{ {
self.base = f(self.base, val); self.base = f(self.base, val);
self self
@@ -165,7 +141,7 @@ impl Config {
// (plz, lets refactor it) // (plz, lets refactor it)
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{ {
self.base = self.base.with_optional(f, val); self.base = self.base.with_optional(f, val);
self self
@@ -173,7 +149,7 @@ impl Config {
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr, T: FromStr,
<T as FromStr>::Err: Debug, <T as FromStr>::Err: Debug,
{ {
@@ -189,7 +165,7 @@ impl Config {
parser: G, parser: G,
) -> Self ) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T, G: Fn(&str) -> T,
{ {
self.base = self.base.with_optional_custom_env(f, val, env_var, parser); self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
@@ -197,19 +173,34 @@ impl Config {
} }
} }
// define_optional_set_inner!(Config, base, BaseClientConfig);
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketType {
WebSocket,
None,
}
impl SocketType {
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct Socket { pub struct Socket {
socket_type: SocketType, pub socket_type: SocketType,
host: IpAddr, pub host: IpAddr,
listening_port: u16, pub listening_port: u16,
} }
impl Default for Socket { impl Default for Socket {
fn default() -> Self { fn default() -> Self {
Socket { Socket {
socket_type: SocketType::WebSocket, socket_type: SocketType::WebSocket,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), host: IpAddr::V4(Ipv4Addr::LOCALHOST),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
} }
} }
@@ -1,58 +1,33 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, Socket}; use crate::client::config::old_config_v1_1_20::{ConfigV1_1_20, SocketV1_1_20};
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::NymConfig; use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 { pub struct OldConfigV1_1_13 {
#[serde(flatten)] #[serde(flatten)]
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>, pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
socket: Socket, pub socket: SocketV1_1_20,
} }
impl NymConfig for OldConfigV1_1_13 { impl MigrationNymConfig for OldConfigV1_1_13 {
fn template() -> &'static str {
// not intended to be used
unimplemented!()
}
fn default_root_directory() -> PathBuf { fn default_root_directory() -> PathBuf {
dirs::home_dir() dirs::home_dir()
.expect("Failed to evaluate $HOME value") .expect("Failed to evaluate $HOME value")
.join(".nym") .join(".nym")
.join("clients") .join("clients")
} }
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.client.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("data")
}
} }
impl From<OldConfigV1_1_13> for Config { impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self { fn from(value: OldConfigV1_1_13) -> Self {
Config { ConfigV1_1_20 {
base: value.base.into(), base: value.base.into(),
socket: value.socket, socket: value.socket,
} }
@@ -0,0 +1,115 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_20_2::{
ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
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::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::{
ClientV1_1_20_2, ConfigV1_1_20_2 as BaseConfigV1_1_20_2,
};
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_20 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_20> for SocketTypeV1_1_20_2 {
fn from(value: SocketTypeV1_1_20) -> Self {
match value {
SocketTypeV1_1_20::WebSocket => SocketTypeV1_1_20_2::WebSocket,
SocketTypeV1_1_20::None => SocketTypeV1_1_20_2::None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socket: SocketV1_1_20,
}
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_20_2 {
base: BaseConfigV1_1_20_2 {
client: ClientV1_1_20_2 {
version: value.base.client.version,
id: value.base.client.id,
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
nyxd_urls: value.base.client.nyxd_urls,
nym_api_urls: value.base.client.nym_api_urls,
gateway_endpoint: value.base.client.gateway_endpoint.into(),
},
debug: value.base.debug.into(),
},
socket: value.socket.into(),
storage_paths: ClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
public_encryption_key_file: value.base.client.public_encryption_key_file,
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
ack_key_file: value.base.client.ack_key_file,
},
credentials_database: value.base.client.database_path,
reply_surb_database: value.base.client.reply_surb_database_path,
},
},
logging: LoggingSettings::default(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_20 {
socket_type: SocketTypeV1_1_20,
host: IpAddr,
listening_port: u16,
}
impl From<SocketV1_1_20> for SocketV1_1_20_2 {
fn from(value: SocketV1_1_20) -> Self {
SocketV1_1_20_2 {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
}
}
}
impl Default for SocketV1_1_20 {
fn default() -> Self {
SocketV1_1_20 {
socket_type: SocketTypeV1_1_20::WebSocket,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
}
}
@@ -0,0 +1,103 @@
// 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 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;
use nym_client_core::config::GatewayEndpointConfig;
use nym_config::read_config_from_toml_file;
use nym_network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use serde::{Deserialize, Serialize};
use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPathsV1_1_20_2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct ConfigV1_1_20_2 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20_2,
pub socket: SocketV1_1_20_2,
pub storage_paths: ClientPathsV1_1_20_2,
pub logging: LoggingSettings,
}
impl ConfigV1_1_20_2 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
// 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) {
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(),
},
logging: self.logging,
};
(config, gateway_details)
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_20_2 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_20_2> for SocketType {
fn from(value: SocketTypeV1_1_20_2) -> Self {
match value {
SocketTypeV1_1_20_2::WebSocket => SocketType::WebSocket,
SocketTypeV1_1_20_2::None => SocketType::None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_20_2 {
pub socket_type: SocketTypeV1_1_20_2,
pub host: IpAddr,
pub listening_port: u16,
}
impl From<SocketV1_1_20_2> for Socket {
fn from(value: SocketV1_1_20_2) -> Self {
Socket {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
}
}
}
impl Default for SocketV1_1_20_2 {
fn default() -> Self {
SocketV1_1_20_2 {
socket_type: SocketTypeV1_1_20_2::WebSocket,
host: IpAddr::V4(Ipv4Addr::LOCALHOST),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
}
}
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
}
impl ClientPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
ClientPaths {
common_paths: CommonClientPaths::new_default(base_data_directory),
}
}
}
+22 -36
View File
@@ -1,12 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub(crate) fn config_template() -> &'static str { // While using normal toml marshalling would have been way simpler with less overhead,
// While using normal toml marshalling would have been way simpler with less overhead, // I think it's useful to have comments attached to the saved config file to explain behaviour of
// I think it's useful to have comments attached to the saved config file to explain behaviour of // particular fields.
// particular fields. // Note: any changes to the template must be reflected in the appropriate structs.
// Note: any changes to the template must be reflected in the appropriate structs. pub(crate) const CONFIG_TEMPLATE: &str = r#"
r#"
# This is a TOML config file. # This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml # For more information, see https://github.com/toml-lang/toml
@@ -37,50 +36,37 @@ nym_api_urls = [
{{/each}} {{/each}}
] ]
[storage_paths]
# Path to file containing private identity key. # Path to file containing private identity key.
private_identity_key_file = '{{ client.private_identity_key_file }}' keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
# Path to file containing public identity key. # Path to file containing public identity key.
public_identity_key_file = '{{ client.public_identity_key_file }}' keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
# Path to file containing private encryption key. # Path to file containing private encryption key.
private_encryption_key_file = '{{ client.private_encryption_key_file }}' keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
# Path to file containing public encryption key. # Path to file containing public encryption key.
public_encryption_key_file = '{{ client.public_encryption_key_file }}' keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for # A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway. # communication with particular gateway.
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an # Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to. # acknowledgement so that nobody besides the client knows which packet it refers to.
ack_key_file = '{{ client.ack_key_file }}' keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
##### advanced configuration options #####
# Absolute path to the home Nym Clients directory. # Path to the database containing bandwidth credentials
nym_root_directory = '{{ client.nym_root_directory }}' credentials_database = '{{ storage_paths.credentials_database }}'
[client.gateway_endpoint]
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
# Address of the gateway owner to which the client should send messages.
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
##### socket config options ##### ##### socket config options #####
@@ -120,5 +106,5 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
[debug.cover_traffic] [debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#
} "#;
+22 -41
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config; use crate::client::config::Config;
@@ -6,19 +6,15 @@ use crate::error::ClientError;
use crate::websocket; use crate::websocket;
use futures::channel::mpsc; use futures::channel::mpsc;
use log::*; use log::*;
use nym_bandwidth_controller::BandwidthController; use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config;
use nym_client_core::client::base_client::non_wasm_helpers::create_bandwidth_controller;
use nym_client_core::client::base_client::storage::OnDiskPersistent; use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_client_core::client::base_client::{ use nym_client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
}; };
use nym_client_core::client::inbound_messages::InputMessage; use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::client::received_buffer::{ use nym_client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
}; };
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType; use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
@@ -46,17 +42,6 @@ impl SocketClient {
SocketClient { config } SocketClient { config }
} }
async fn create_bandwidth_controller(
config: &Config,
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
let storage = nym_credential_storage::initialise_persistent_storage(
config.get_base().get_database_path(),
)
.await;
create_bandwidth_controller(config.get_base(), storage)
}
fn start_websocket_listener( fn start_websocket_listener(
config: &Config, config: &Config,
client_input: ClientInput, client_input: ClientInput,
@@ -93,7 +78,7 @@ impl SocketClient {
Some(packet_type), Some(packet_type),
); );
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port()) websocket::Listener::new(config.socket.host, config.socket.listening_port)
.start(websocket_handler, shutdown); .start(websocket_handler, shutdown);
} }
@@ -106,42 +91,38 @@ impl SocketClient {
res res
} }
fn key_store(&self) -> OnDiskKeys { async fn initialise_storage(&self) -> Result<OnDiskPersistent, ClientError> {
let pathfinder = ClientKeyPathfinder::new_from_config(self.config.get_base()); Ok(OnDiskPersistent::from_paths(
OnDiskKeys::new(pathfinder) self.config.storage_paths.common_paths.clone(),
&self.config.base.debug,
)
.await?)
} }
// TODO: see if this could also be shared with socks5 client / nym-sdk maybe // TODO: see if this could also be shared with socks5 client / nym-sdk maybe
async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> { async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> {
// don't create bandwidth controller if credentials are disabled // don't create dkg client for the bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() { let dkg_query_client = if self.config.base.client.disabled_credentials_mode {
None None
} else { } else {
Some(Self::create_bandwidth_controller(&self.config).await) Some(default_query_dkg_client_from_config(&self.config.base))
}; };
let base_client = BaseClientBuilder::new_from_base_config( let storage = self.initialise_storage().await?;
self.config.get_base(),
self.key_store(), let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
self.config.get_base().get_reply_surb_database_path(),
&self.config.get_debug_settings().reply_surbs,
)
.await?,
);
Ok(base_client) Ok(base_client)
} }
pub async fn start_socket(self) -> Result<TaskManager, ClientError> { pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
if !self.config.get_socket_type().is_websocket() { if !self.config.socket.socket_type.is_websocket() {
return Err(ClientError::InvalidSocketMode); return Err(ClientError::InvalidSocketMode);
} }
let base_builder = self.create_base_client_builder().await?; let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.get_base().get_packet_type(); let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base(packet_type).await?; let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address; let self_address = started_client.address;
let client_input = started_client.client_input.register_producer(); let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer(); let client_output = started_client.client_output.register_consumer();
@@ -164,13 +145,13 @@ impl SocketClient {
} }
pub async fn start_direct(self) -> Result<DirectClient, ClientError> { pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() { if self.config.socket.socket_type.is_websocket() {
return Err(ClientError::InvalidSocketMode); return Err(ClientError::InvalidSocketMode);
} }
let base_builder = self.create_base_client_builder().await?; let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.get_base().get_packet_type(); let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base(packet_type).await?; let mut started_client = base_builder.start_base().await?;
let address = started_client.address; let address = started_client.address;
let client_input = started_client.client_input.register_producer(); let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer(); let client_output = started_client.client_output.register_consumer();
+47 -37
View File
@@ -1,7 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config; use crate::client::config::{
default_config_directory, default_config_filepath, default_data_directory,
};
use crate::commands::try_upgrade_config;
use crate::{ use crate::{
client::config::Config, client::config::Config,
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
@@ -9,13 +12,16 @@ use crate::{
}; };
use clap::Args; use clap::Args;
use nym_bin_common::output_format::OutputFormat; use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig; use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize; use serde::Serialize;
use std::fmt::Display; use std::fmt::Display;
use std::net::IpAddr; use std::net::IpAddr;
use std::{fs, io};
use tap::TapFallible; use tap::TapFallible;
#[derive(Args, Clone)] #[derive(Args, Clone)]
@@ -97,15 +103,15 @@ impl From<Init> for OverrideConfig {
pub struct InitResults { pub struct InitResults {
#[serde(flatten)] #[serde(flatten)]
client_core: nym_client_core::init::InitResults, client_core: nym_client_core::init::InitResults,
client_listening_port: String, client_listening_port: u16,
client_address: String, client_address: String,
} }
impl InitResults { impl InitResults {
fn new(config: &Config, address: &Recipient) -> Self { fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
Self { Self {
client_core: nym_client_core::init::InitResults::new(config.get_base(), address), client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway),
client_listening_port: config.get_listening_port().to_string(), client_listening_port: config.socket.listening_port,
client_address: address.to_string(), client_address: address.to_string(),
} }
} }
@@ -119,18 +125,26 @@ impl Display for InitResults {
} }
} }
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
eprintln!("Initialising client..."); eprintln!("Initialising client...");
let id = &args.id; let id = &args.id;
let already_init = Config::default_config_file_path(id).exists(); let already_init = if default_config_filepath(id).exists() {
if already_init {
// in case we're using old config, try to upgrade it // in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op) // (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?; try_upgrade_config(id)?;
eprintln!("Client \"{id}\" was already initialised before"); eprintln!("Client \"{id}\" was already initialised before");
} true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force // Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted. // re-registering if wanted.
@@ -146,48 +160,44 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
// Attempt to use a user-provided gateway, if possible // Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway; let user_chosen_gateway_id = args.gateway;
let gateway_setup = GatewaySetup::new_fresh(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
);
// Load and potentially override config // Load and potentially override config
let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone())); let config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
// Setup gateway by either registering a new one, or creating a new config from the selected // Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration. // one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::from_config(config.get_base()); let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>( let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
&gateway_setup,
&key_store, &key_store,
&details_store,
register_gateway, register_gateway,
user_chosen_gateway_id, Some(&config.base.client.nym_api_urls),
config.get_base(),
args.latency_based_selection,
) )
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().set_gateway_endpoint(gateway); let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
config.save_to_file(None).tap_err(|_| {
log::error!("Failed to save the config file"); log::error!("Failed to save the config file");
})?; })?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
print_saved_config(&config); let address = init_details.client_address()?;
let address = eprintln!("Client configuration completed.\n");
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address); let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
println!("{}", args.output.format(&init_results)); println!("{}", args.output.format(&init_results));
Ok(()) Ok(())
} }
fn print_saved_config(config: &Config) {
let config_save_location = config.get_config_file_save_location();
eprintln!("Saved configuration file to {config_save_location:?}");
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
log::debug!(
"Gateway listener: {}",
config.get_base().get_gateway_listener()
);
eprintln!("Client configuration completed.\n");
}
+133 -13
View File
@@ -2,14 +2,23 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13; use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::{BaseConfig, Config}; use crate::client::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::client::config::{BaseClientConfig, Config};
use crate::error::ClientError;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::info; use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet}; use nym_client_core::client::base_client::storage::gateway_details::{
OnDiskGatewayDetails, PersistedGatewayDetails,
};
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_config::OptionalSet;
use std::error::Error; use std::error::Error;
use std::net::IpAddr; use std::net::IpAddr;
@@ -82,40 +91,151 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
config config
.with_optional(Config::with_disabled_socket, args.disable_socket) .with_optional(Config::with_disabled_socket, args.disable_socket)
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) .with_base(
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) BaseClientConfig::with_high_default_traffic_volume,
args.fastmode,
)
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_optional(Config::with_port, args.port) .with_optional(Config::with_port, args.port)
.with_optional(Config::with_host, args.host) .with_optional(Config::with_host, args.host)
.with_optional_custom_env_ext( .with_optional_custom_env_ext(
BaseConfig::with_custom_nym_apis, BaseClientConfig::with_custom_nym_apis,
args.nym_apis, args.nym_apis,
nym_network_defaults::var_names::NYM_API, nym_network_defaults::var_names::NYM_API,
nym_config::parse_urls, nym_config::parse_urls,
) )
.with_optional_custom_env_ext( .with_optional_custom_env_ext(
BaseConfig::with_custom_nyxd, BaseClientConfig::with_custom_nyxd,
args.nyxd_urls, args.nyxd_urls,
nym_network_defaults::var_names::NYXD, nym_network_defaults::var_names::NYXD,
nym_config::parse_urls, nym_config::parse_urls,
) )
.with_optional_ext( .with_optional_ext(
BaseConfig::with_disabled_credentials, BaseClientConfig::with_disabled_credentials,
args.enabled_credentials_mode.map(|b| !b), args.enabled_credentials_mode.map(|b| !b),
) )
} }
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> { fn persist_gateway_details(
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+) config: &Config,
details: GatewayEndpointConfig,
) -> Result<(), ClientError> {
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| {
ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
})
})?;
let persisted_details = PersistedGatewayDetails::new(details, &shared_keys);
details_store
.store_to_disk(&persisted_details)
.map_err(|source| {
ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
})
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else { let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade // if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day // or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(()); return Ok(false);
}; };
info!("It seems the client is using <= v1.1.13 config template."); info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification."); info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into(); let updated_step1: ConfigV1_1_20 = old_config.into();
updated.save_to_file(None) let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.20 config template.");
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();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
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();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
return Ok(());
}
Ok(())
}
fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
// try to load the config as is
if let Ok(cfg) = Config::read_from_default_path(id) {
return if !cfg.validate() {
Err(ClientError::ConfigValidationFailure)
} else {
Ok(cfg)
};
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
let config = match Config::read_from_default_path(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
return Err(ClientError::FailedToLoadConfig(id.to_string()));
}
};
if !config.validate() {
return Err(ClientError::ConfigValidationFailure);
}
Ok(config)
} }
#[cfg(test)] #[cfg(test)]
+7 -29
View File
@@ -1,10 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::error::Error; use crate::commands::try_load_current_config;
use std::net::IpAddr;
use crate::commands::try_upgrade_v1_1_13_config;
use crate::{ use crate::{
client::{config::Config, SocketClient}, client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
@@ -13,8 +10,9 @@ use crate::{
use clap::Args; use clap::Args;
use log::*; use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible; use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use std::error::Error;
use std::net::IpAddr;
#[derive(Args, Clone)] #[derive(Args, Clone)]
pub(crate) struct Run { pub(crate) struct Run {
@@ -82,7 +80,7 @@ impl From<Run> for OverrideConfig {
// network version. It might do so in the future. // network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool { fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION"); let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version(); let config_version = &cfg.base.client.version;
if binary_version == config_version { if binary_version == config_version {
true true
} else { } else {
@@ -98,30 +96,10 @@ fn version_check(cfg: &Config) -> bool {
} }
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> { pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
let id = &args.id; eprintln!("Starting client {}...", args.id);
// in case we're using old config, try to upgrade it let mut config = try_load_current_config(&args.id)?;
// (if we're using the current version, it's a no-op) config = override_config(config, OverrideConfig::from(args.clone()));
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string())));
}
};
if !config.validate() {
return Err(Box::new(ClientError::ConfigValidationFailure));
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if config.get_base_mut().set_empty_fields_to_defaults() {
warn!("some of the core config options were left unset. the default values are going to get used instead.");
}
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); error!("failed the local version check");
+16 -84
View File
@@ -1,42 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, MISSING_VALUE}; use crate::client::config::Config;
use crate::commands::try_load_current_config;
use nym_bin_common::version_checker::Version;
use nym_config::NymConfig;
use clap::Args; use clap::Args;
use std::fmt::Display; use nym_bin_common::version_checker::Version;
use std::process; use std::process;
#[allow(dead_code)] fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! { eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
print_failed_upgrade(from_version, to_version);
process::exit(1)
}
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!("\n==================\nTrying to upgrade client from {from} to {to} ...");
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!("Upgrade from {from} to {to} failed!\n==================\n");
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!("Upgrade from {from} to {to} was successful!\n==================\n");
}
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
eprintln!(
"Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!"
);
process::exit(1)
}
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {config_version} to {current_version}. Please let the developers know about this issue if you expected it to work!");
process::exit(1) process::exit(1)
} }
@@ -48,7 +20,7 @@ pub(crate) struct Upgrade {
} }
fn parse_config_version(config: &Config) -> Version { fn parse_config_version(config: &Config) -> Version {
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { let version = Version::parse(&config.base.client.version).unwrap_or_else(|err| {
eprintln!("failed to parse client version! - {err}"); eprintln!("failed to parse client version! - {err}");
process::exit(1) process::exit(1)
}); });
@@ -77,53 +49,14 @@ fn parse_package_version() -> Version {
version version
} }
fn minor_0_12_upgrade( fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
mut config: Config, let config_version = parse_config_version(&config);
_matches: &Upgrade, if &config_version == package_version {
config_version: &Version, println!("You're using the most recent version!");
package_version: &Version, return;
) -> Config {
let to_version = if package_version.major == 0 && package_version.minor == 12 {
package_version.clone()
} else {
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
print_successful_upgrade(config_version, to_version);
config
}
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, package_version),
}
} }
unimplemented_upgrade(package_version, &config_version)
} }
pub(crate) fn execute(args: &Upgrade) { pub(crate) fn execute(args: &Upgrade) {
@@ -131,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id; let id = &args.id;
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}"); eprintln!("failed to load existing config file! - {err}");
process::exit(1) process::exit(1)
}); });
if existing_config.get_base().get_version() == MISSING_VALUE { if existing_config.base.client.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number."); eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1); process::exit(1);
} }
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, args, &package_version) do_upgrade(existing_config, args, &package_version)
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.20" version = "1.1.22"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] 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" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
+48 -37
View File
@@ -1,20 +1,25 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config; use crate::commands::try_upgrade_config;
use crate::config::{
default_config_directory, default_config_filepath, default_data_directory, Config,
};
use crate::{ use crate::{
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::Socks5ClientError, error::Socks5ClientError,
}; };
use clap::Args; use clap::Args;
use nym_bin_common::output_format::OutputFormat; use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_config::NymConfig; use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::config::Config;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize; use serde::Serialize;
use std::fmt::Display; use std::fmt::Display;
use std::{fs, io};
use tap::TapFallible; use tap::TapFallible;
#[derive(Args, Clone)] #[derive(Args, Clone)]
@@ -100,15 +105,19 @@ impl From<Init> for OverrideConfig {
pub struct InitResults { pub struct InitResults {
#[serde(flatten)] #[serde(flatten)]
client_core: nym_client_core::init::InitResults, client_core: nym_client_core::init::InitResults,
socks5_listening_port: String, socks5_listening_port: u16,
client_address: String, client_address: String,
} }
impl InitResults { impl InitResults {
fn new(config: &Config, address: &Recipient) -> Self { fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
Self { Self {
client_core: nym_client_core::init::InitResults::new(config.get_base(), address), client_core: nym_client_core::init::InitResults::new(
socks5_listening_port: config.get_socks5().get_listening_port().to_string(), &config.core.base,
address,
gateway,
),
socks5_listening_port: config.core.socks5.listening_port,
client_address: address.to_string(), client_address: address.to_string(),
} }
} }
@@ -122,19 +131,27 @@ impl Display for InitResults {
} }
} }
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client..."); eprintln!("Initialising client...");
let id = &args.id; let id = &args.id;
let provider_address = &args.provider; let provider_address = &args.provider;
let already_init = Config::default_config_file_path(id).exists(); let already_init = if default_config_filepath(id).exists() {
if already_init {
// in case we're using old config, try to upgrade it // in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op) // (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?; try_upgrade_config(id)?;
eprintln!("SOCKS5 client \"{id}\" was already initialised before"); eprintln!("SOCKS5 client \"{id}\" was already initialised before");
} true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force // Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted. // re-registering if wanted.
@@ -150,53 +167,47 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
// Attempt to use a user-provided gateway, if possible // Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway; let user_chosen_gateway_id = args.gateway;
let gateway_setup = GatewaySetup::new_fresh(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
);
// Load and potentially override config // Load and potentially override config
let mut config = override_config( let config = override_config(
Config::new(id, &provider_address.to_string()), Config::new(id, &provider_address.to_string()),
OverrideConfig::from(args.clone()), OverrideConfig::from(args.clone()),
); );
// Setup gateway by either registering a new one, or creating a new config from the selected // Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration. // one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::from_config(config.get_base()); let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, _>( let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
&gateway_setup,
&key_store, &key_store,
&details_store,
register_gateway, register_gateway,
user_chosen_gateway_id, Some(&config.core.base.client.nym_api_urls),
config.get_base(),
args.latency_based_selection,
) )
.await .await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().set_gateway_endpoint(gateway);
// TODO: ask the service provider we specified for its interface version and set it in the config // TODO: ask the service provider we specified for its interface version and set it in the config
config.save_to_file(None).tap_err(|_| { let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file"); log::error!("Failed to save the config file");
})?; })?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
print_saved_config(&config); let address = init_details.client_address()?;
let address = let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
println!("{}", args.output.format(&init_results)); println!("{}", args.output.format(&init_results));
Ok(()) Ok(())
} }
fn print_saved_config(config: &Config) {
let config_save_location = config.get_config_file_save_location();
eprintln!("Saved configuration file to {:?}", config_save_location);
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
log::debug!(
"Gateway listener: {}",
config.get_base().get_gateway_listener()
);
eprintln!("Client configuration completed.\n");
}
+138 -18
View File
@@ -1,15 +1,24 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::info; use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet}; use nym_client_core::client::base_client::storage::gateway_details::{
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13; OnDiskGatewayDetails, PersistedGatewayDetails,
use nym_socks5_client_core::config::{BaseConfig, Config}; };
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_config::OptionalSet;
use nym_sphinx::params::PacketType; use nym_sphinx::params::PacketType;
use std::error::Error; use std::error::Error;
@@ -88,41 +97,152 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
PacketType::Mix PacketType::Mix
}; };
config config
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) .with_base(
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) BaseClientConfig::with_high_default_traffic_volume,
.with_base(BaseConfig::with_packet_type, packet_type) args.fastmode,
)
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseClientConfig::with_packet_type, packet_type)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port) .with_optional(Config::with_port, args.port)
.with_optional_custom_env_ext( .with_optional_base_custom_env(
BaseConfig::with_custom_nym_apis, BaseClientConfig::with_custom_nym_apis,
args.nym_apis, args.nym_apis,
nym_network_defaults::var_names::NYM_API, nym_network_defaults::var_names::NYM_API,
nym_config::parse_urls, nym_config::parse_urls,
) )
.with_optional_custom_env_ext( .with_optional_base_custom_env(
BaseConfig::with_custom_nyxd, BaseClientConfig::with_custom_nyxd,
args.nyxd_urls, args.nyxd_urls,
nym_network_defaults::var_names::NYXD, nym_network_defaults::var_names::NYXD,
nym_config::parse_urls, nym_config::parse_urls,
) )
.with_optional_ext( .with_optional_base(
BaseConfig::with_disabled_credentials, BaseClientConfig::with_disabled_credentials,
args.enabled_credentials_mode.map(|b| !b), args.enabled_credentials_mode.map(|b| !b),
) )
} }
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> { fn persist_gateway_details(
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+) config: &Config,
details: GatewayEndpointConfig,
) -> Result<(), Socks5ClientError> {
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| {
Socks5ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
})
})?;
let persisted_details = PersistedGatewayDetails::new(details, &shared_keys);
details_store
.store_to_disk(&persisted_details)
.map_err(|source| {
Socks5ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
})
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else { let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade // if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day // or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(()); return Ok(false);
}; };
info!("It seems the client is using <= v1.1.13 config template."); info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification."); info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into(); let updated_step1: ConfigV1_1_20 = old_config.into();
updated.save_to_file(None) let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.20 config template.");
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();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
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();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
return Ok(());
}
Ok(())
}
fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
// try to load the config as is
if let Ok(cfg) = Config::read_from_default_path(id) {
return if !cfg.validate() {
Err(Socks5ClientError::ConfigValidationFailure)
} else {
Ok(cfg)
};
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
let config = match Config::read_from_default_path(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
}
};
if !config.validate() {
return Err(Socks5ClientError::ConfigValidationFailure);
}
Ok(config)
} }
#[cfg(test)] #[cfg(test)]
+13 -38
View File
@@ -1,7 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config; use crate::commands::try_load_current_config;
use crate::config::Config;
use crate::{ use crate::{
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::Socks5ClientError, error::Socks5ClientError,
@@ -10,9 +11,8 @@ use clap::Args;
use log::*; use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible; use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_client_core::client::base_client::storage::OnDiskPersistent; use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::{config::Config, NymClient}; use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
#[derive(Args, Clone)] #[derive(Args, Clone)]
@@ -21,10 +21,6 @@ pub(crate) struct Run {
#[clap(long)] #[clap(long)]
id: String, id: String,
/// Custom path to the nym-mixnet-client configuration file
#[clap(long)]
config: Option<String>,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
/// While this is going to hide its actual address information, it will make the actual communication /// While this is going to hide its actual address information, it will make the actual communication
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs. /// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
@@ -92,13 +88,12 @@ impl From<Run> for OverrideConfig {
// network version. It might do so in the future. // network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool { fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION"); let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version(); let config_version = &cfg.core.base.client.version;
if binary_version == config_version { if binary_version == config_version {
true true
} else { } else {
warn!( warn!(
"The mixnode binary has different version than what is specified in config file! {} and {}", "The socks5-client binary has different version than what is specified in config file! {binary_version} and {config_version}",
binary_version, config_version
); );
if is_minor_version_compatible(binary_version, config_version) { if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command"); info!("but they are still semver compatible. However, consider running the `upgrade` command");
@@ -111,38 +106,18 @@ fn version_check(cfg: &Config) -> bool {
} }
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let id = &args.id; eprintln!("Starting client {}...", args.id);
// in case we're using old config, try to upgrade it let mut config = try_load_current_config(&args.id)?;
// (if we're using the current version, it's a no-op) config = override_config(config, OverrideConfig::from(args.clone()));
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
id.to_string(),
)));
}
};
if !config.validate() {
return Err(Box::new(Socks5ClientError::ConfigValidationFailure));
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if config.get_base_mut().set_empty_fields_to_defaults() {
warn!("some of the core config options were left unset. the default values are going to get used instead.");
}
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); error!("failed the local version check");
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck)); return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
} }
let storage = OnDiskPersistent::from_config(config.get_base()).await?; let storage =
NymClient::new(config, storage).run_forever().await OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
.await?;
NymClient::new(config.core, storage).run_forever().await
} }
+19 -99
View File
@@ -1,50 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use nym_bin_common::version_checker::Version; use crate::commands::try_load_current_config;
use nym_config::NymConfig; use crate::config::Config;
use nym_socks5_client_core::config::{Config, MISSING_VALUE};
use clap::Args; use clap::Args;
use std::{fmt::Display, process}; use nym_bin_common::version_checker::Version;
use std::process;
#[allow(dead_code)] fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! { eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
print_failed_upgrade(from_version, to_version);
process::exit(1)
}
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade client from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
eprintln!(
"Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!",
config_version, package_version
);
process::exit(1)
}
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version);
process::exit(1) process::exit(1)
} }
@@ -56,15 +20,14 @@ pub(crate) struct Upgrade {
} }
fn parse_config_version(config: &Config) -> Version { fn parse_config_version(config: &Config) -> Version {
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { let version = Version::parse(&config.core.base.client.version).unwrap_or_else(|err| {
eprintln!("failed to parse client version! - {err}"); eprintln!("failed to parse client version! - {err}");
process::exit(1) process::exit(1)
}); });
if version.is_prerelease() || !version.build.is_empty() { if version.is_prerelease() || !version.build.is_empty() {
eprintln!( eprintln!(
"Trying to upgrade from a non-released version {}. This is not supported!", "Trying to upgrade from a non-released version {version}. This is not supported!"
version
); );
process::exit(1) process::exit(1)
} }
@@ -79,63 +42,21 @@ fn parse_package_version() -> Version {
// however, we are not using them ourselves at the moment and hence it should be fine. // however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code // if we change our mind, we could easily tweak this code
if version.is_prerelease() || !version.build.is_empty() { if version.is_prerelease() || !version.build.is_empty() {
eprintln!( eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!");
"Trying to upgrade to a non-released version {}. This is not supported!",
version
);
process::exit(1) process::exit(1)
} }
version version
} }
fn minor_0_12_upgrade( fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
mut config: Config, let config_version = parse_config_version(&config);
_args: &Upgrade, if &config_version == package_version {
config_version: &Version, println!("You're using the most recent version!");
package_version: &Version, return;
) -> Config {
let to_version = if package_version.major == 0 && package_version.minor == 12 {
package_version.clone()
} else {
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
print_successful_upgrade(config_version, to_version);
config
}
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, package_version),
}
} }
unimplemented_upgrade(package_version, &config_version)
} }
pub(crate) fn execute(args: &Upgrade) { pub(crate) fn execute(args: &Upgrade) {
@@ -143,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id; let id = &args.id;
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}"); eprintln!("failed to load existing config file! - {err}");
process::exit(1) process::exit(1)
}); });
if existing_config.get_base().get_version() == MISSING_VALUE { if existing_config.core.base.client.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number."); eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1); process::exit(1);
} }
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, args, &package_version) do_upgrade(existing_config, args, &package_version)
} }
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_socks5_client_core::config::Config as CoreConfig;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template;
const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients";
/// Derive default path to clients's config directory.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to client's config file.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
/// Derive default path to client's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub core: CoreConfig,
pub storage_paths: SocksClientPaths,
pub logging: LoggingSettings,
}
impl NymConfigTemplate for Config {
fn template() -> &'static str {
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
Config {
core: CoreConfig::new(
id.as_ref(),
env!("CARGO_PKG_VERSION"),
provider_mix_address.as_ref(),
),
storage_paths: SocksClientPaths::new_default(default_data_directory(id.as_ref())),
logging: Default::default(),
}
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.core.base.client.id)
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.core.validate()
}
pub fn with_port(mut self, port: u16) -> Self {
self.core.socks5.listening_port = port;
self
}
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
self.core.socks5.send_anonymously = anonymous_replies;
self
}
// poor man's 'builder' method
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.core = self.core.with_base(f, val);
self
}
pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.core = self.core.with_optional_base(f, val);
self
}
#[allow(unused)]
pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr,
<T as FromStr>::Err: Debug,
{
self.core = self.core.with_optional_base_env(f, val, env_var);
self
}
pub fn with_optional_base_custom_env<F, T, G>(
mut self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T,
{
self.core = self
.core
.with_optional_base_custom_env(f, val, env_var, parser);
self
}
}
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20::{ConfigV1_1_20, Socks5V1_1_20};
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use nym_config::must_get_home;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
#[serde(flatten)]
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub socks5: Socks5V1_1_20,
}
impl MigrationNymConfig for OldConfigV1_1_13 {
fn default_root_directory() -> PathBuf {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let base_dir = must_get_home();
#[cfg(any(target_os = "android", target_os = "ios"))]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
}
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
ConfigV1_1_20 {
base: value.base.into(),
socks5: value.socks5,
}
}
}
@@ -0,0 +1,137 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
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::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use nym_config::must_get_home;
use nym_socks5_client_core::config::old_config_v1_1_20_2::{
BaseClientConfigV1_1_20_2, Socks5DebugV1_1_20_2, Socks5V1_1_20_2,
};
use nym_socks5_client_core::config::{ProviderInterfaceVersion, Socks5ProtocolVersion};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::path::PathBuf;
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socks5: Socks5V1_1_20,
}
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_20_2 {
core: CoreConfigV1_1_20_2 {
base: BaseClientConfigV1_1_20_2 {
client: ClientV1_1_20_2 {
version: value.base.client.version,
id: value.base.client.id,
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
nyxd_urls: value.base.client.nyxd_urls,
nym_api_urls: value.base.client.nym_api_urls,
gateway_endpoint: value.base.client.gateway_endpoint.into(),
},
debug: value.base.debug.into(),
},
socks5: value.socks5.into(),
},
storage_paths: SocksClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
public_encryption_key_file: value.base.client.public_encryption_key_file,
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
ack_key_file: value.base.client.ack_key_file,
},
credentials_database: value.base.client.database_path,
reply_surb_database: value.base.client.reply_surb_database_path,
},
},
logging: LoggingSettings::default(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let base_dir = must_get_home();
#[cfg(any(target_os = "android", target_os = "ios"))]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5V1_1_20 {
pub listening_port: u16,
pub provider_mix_address: String,
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
pub socks5_protocol_version: Socks5ProtocolVersion,
#[serde(default)]
pub send_anonymously: bool,
#[serde(default)]
pub socks5_debug: Socks5DebugV1_1_20,
}
impl From<Socks5V1_1_20> for Socks5V1_1_20_2 {
fn from(value: Socks5V1_1_20) -> Self {
Socks5V1_1_20_2 {
listening_port: value.listening_port,
provider_mix_address: value.provider_mix_address,
provider_interface_version: value.provider_interface_version,
socks5_protocol_version: value.socks5_protocol_version,
send_anonymously: value.send_anonymously,
socks5_debug: value.socks5_debug.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5DebugV1_1_20 {
connection_start_surbs: u32,
per_request_surbs: u32,
}
impl From<Socks5DebugV1_1_20> for Socks5DebugV1_1_20_2 {
fn from(value: Socks5DebugV1_1_20) -> Self {
Socks5DebugV1_1_20_2 {
connection_start_surbs: value.connection_start_surbs,
per_request_surbs: value.per_request_surbs,
}
}
}
impl Default for Socks5DebugV1_1_20 {
fn default() -> Self {
Socks5DebugV1_1_20 {
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
}
}
}
@@ -0,0 +1,54 @@
// 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 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;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct SocksClientPathsV1_1_20_2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
pub core: CoreConfigV1_1_20_2,
pub storage_paths: SocksClientPathsV1_1_20_2,
pub logging: LoggingSettings,
}
impl ConfigV1_1_20_2 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
// 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) {
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(),
},
logging: self.logging,
};
(config, gateway_details)
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct SocksClientPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
}
impl SocksClientPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
SocksClientPaths {
common_paths: CommonClientPaths::new_default(base_data_directory),
}
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base client config options #####
[core.client]
# Version of the client for which this configuration was created.
version = '{{ core.client.version }}'
# Human readable ID of this particular client.
id = '{{ core.client.id }}'
# Indicates whether this client is running in a disabled credentials mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ core.client.disabled_credentials_mode }}
# Addresses to nyxd validators via which the client can communicate with the chain.
nyxd_urls = [
{{#each core.client.nyxd_urls }}
'{{this}}',
{{/each}}
]
# Addresses to APIs running on validator from which the client gets the view of the network.
nym_api_urls = [
{{#each core.client.nym_api_urls }}
'{{this}}',
{{/each}}
]
[storage_paths]
# Path to file containing private identity key.
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
# Path to file containing public identity key.
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
# Path to file containing private encryption key.
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to.
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
# Path to the database containing bandwidth credentials
credentials_database = '{{ storage_paths.credentials_database }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
##### socket config options #####
[core.socks5]
# The mix address of the provider to which all requests are going to be sent.
provider_mix_address = '{{ core.socks5.provider_mix_address }}'
# The port on which the client will be listening for incoming requests
listening_port = {{ core.socks5.listening_port }}
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
# While this is going to hide its actual address information, it will make the actual communication
# slower and consume nearly double the bandwidth as it will require sending reply SURBs.
#
# Note that some service providers might not support this.
send_anonymously = {{ core.socks5.send_anonymously }}
##### logging configuration options #####
[logging]
# TODO
##### debug configuration options #####
# The following options should not be modified unless you know EXACTLY what you are doing
# as if set incorrectly, they may impact your anonymity.
# [core.socks5.socks5_debug]
[core.debug]
[core.debug.traffic]
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
[core.debug.acknowledgements]
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
[core.debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#;
+1
View File
@@ -8,6 +8,7 @@ use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_network_defaults::setup_env; use nym_network_defaults::setup_env;
mod commands; mod commands;
mod config;
pub mod error; pub mod error;
#[tokio::main] #[tokio::main]
+104 -22
View File
@@ -62,7 +62,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@@ -225,9 +225,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
@@ -622,7 +622,7 @@ dependencies = [
"cosmos-sdk-proto", "cosmos-sdk-proto",
"ecdsa", "ecdsa",
"eyre", "eyre",
"getrandom 0.2.9", "getrandom 0.2.10",
"k256", "k256",
"prost", "prost",
"prost-types", "prost-types",
@@ -995,7 +995,16 @@ version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys 0.3.7",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys 0.4.1",
] ]
[[package]] [[package]]
@@ -1009,6 +1018,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "dotenv" name = "dotenv"
version = "0.15.0" version = "0.15.0"
@@ -1421,9 +1442,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.9" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"js-sys", "js-sys",
@@ -2228,6 +2249,7 @@ dependencies = [
"log", "log",
"pretty_env_logger", "pretty_env_logger",
"semver 0.11.0", "semver 0.11.0",
"serde",
"vergen", "vergen",
] ]
@@ -2236,8 +2258,9 @@ name = "nym-client-core"
version = "1.1.14" version = "1.1.14"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.21.2",
"dashmap", "dashmap",
"dirs", "dirs 4.0.0",
"futures", "futures",
"gloo-timers", "gloo-timers",
"humantime-serde", "humantime-serde",
@@ -2258,6 +2281,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.10.6",
"sqlx 0.6.3", "sqlx 0.6.3",
"tap", "tap",
"thiserror", "thiserror",
@@ -2321,7 +2345,7 @@ dependencies = [
"bs58", "bs58",
"digest 0.9.0", "digest 0.9.0",
"ff 0.11.1", "ff 0.11.1",
"getrandom 0.2.9", "getrandom 0.2.10",
"group 0.11.0", "group 0.11.0",
"itertools", "itertools",
"nym-dkg", "nym-dkg",
@@ -2370,12 +2394,12 @@ dependencies = [
name = "nym-config" name = "nym-config"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "dirs 5.0.1",
"handlebars", "handlebars",
"log", "log",
"nym-network-defaults", "nym-network-defaults",
"serde", "serde",
"toml", "toml 0.7.4",
"url", "url",
] ]
@@ -2465,7 +2489,7 @@ name = "nym-gateway-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"log", "log",
"nym-bandwidth-controller", "nym-bandwidth-controller",
"nym-coconut-interface", "nym-coconut-interface",
@@ -2612,7 +2636,7 @@ dependencies = [
"chacha20", "chacha20",
"chacha20poly1305", "chacha20poly1305",
"curve25519-dalek", "curve25519-dalek",
"getrandom 0.2.9", "getrandom 0.2.10",
"log", "log",
"rand 0.7.3", "rand 0.7.3",
"rayon", "rayon",
@@ -2792,7 +2816,7 @@ dependencies = [
"aes-gcm", "aes-gcm",
"argon2", "argon2",
"generic-array 0.14.7", "generic-array 0.14.7",
"getrandom 0.2.9", "getrandom 0.2.10",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
@@ -2962,6 +2986,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "pairing" name = "pairing"
version = "0.20.0" version = "0.20.0"
@@ -3372,7 +3402,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
] ]
[[package]] [[package]]
@@ -3440,7 +3470,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"redox_syscall 0.2.16", "redox_syscall 0.2.16",
"thiserror", "thiserror",
] ]
@@ -3468,7 +3498,7 @@ version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.2",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@@ -3602,7 +3632,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.2",
] ]
[[package]] [[package]]
@@ -3834,6 +3864,15 @@ dependencies = [
"syn 2.0.16", "syn 2.0.16",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -4317,7 +4356,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tendermint", "tendermint",
"toml", "toml 0.5.11",
"url", "url",
] ]
@@ -4349,7 +4388,7 @@ dependencies = [
"bytes", "bytes",
"flex-error", "flex-error",
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"http", "http",
"hyper", "hyper",
"hyper-proxy", "hyper-proxy",
@@ -4554,6 +4593,40 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@@ -4715,7 +4788,7 @@ version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [ dependencies = [
"getrandom 0.2.9", "getrandom 0.2.10",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -4891,7 +4964,7 @@ name = "wasm-utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures", "futures",
"getrandom 0.2.9", "getrandom 0.2.10",
"indexed_db_futures", "indexed_db_futures",
"js-sys", "js-sys",
"nym-store-cipher", "nym-store-cipher",
@@ -5142,6 +5215,15 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"
+1 -1
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "nym-client-wasm" name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.1.0" version = "1.1.1"
edition = "2021" edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0" license = "Apache-2.0"
+8 -8
View File
@@ -107,20 +107,20 @@ function printAndDisplayTestResult(result) {
} }
async function testWithTester() { async function testWithTester() {
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
// A) construct with hardcoded topology // A) construct with hardcoded topology
const topology = dummyTopology() const topology = dummyTopology()
const nodeTester = await new NymNodeTester(topology, dummyGateway); const nodeTester = await new NymNodeTester(topology, preferredGateway);
// B) first get topology directly from nym-api // B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; // const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator) // const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, dummyGateway); // 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) // 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 validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, dummyGateway) // const nodeTester = await NymNodeTester.new_with_api(validator, 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 // 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 topology = dummyTopology()
@@ -142,7 +142,7 @@ async function testWithTester() {
} }
async function testWithNymClient() { async function testWithNymClient() {
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const topology = dummyTopology() const topology = dummyTopology()
let received = 0 let received = 0
@@ -164,7 +164,7 @@ async function testWithNymClient() {
console.log('Instantiating WASM client...'); console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, dummyGateway) let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, preferredGateway)
console.log('Web worker creating WASM client...'); console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client(); let local_client = await clientBuilder.start_client();
console.log('WASM client running!'); console.log('WASM client running!');
@@ -222,10 +222,10 @@ async function normalNymClientUsage() {
debug.topology_refresh_rate_ms = BigInt(60000) debug.topology_refresh_rate_ms = BigInt(60000)
const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, dummyGateway, debug); const config = new Config('my-awesome-wasm-client', validator, debug);
const onMessageHandler = (message) => { const onMessageHandler = (message) => {
console.log(message); console.log(message);
+72 -86
View File
@@ -7,73 +7,49 @@
#![allow(clippy::drop_copy)] #![allow(clippy::drop_copy)]
use nym_client_core::config::{ use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic, Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection, CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology, Traffic as ConfigTraffic, GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
Topology as ConfigTopology, Traffic as ConfigTraffic,
}; };
use nym_sphinx::params::{PacketSize, PacketType}; use nym_sphinx::params::{PacketSize, PacketType};
use nym_validator_client::client::IdentityKey;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use url::Url;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Deserialize, PartialEq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Config { pub struct Config {
/// ID specifies the human readable ID of this particular client. pub(crate) base: BaseClientConfig,
pub(crate) id: String,
pub(crate) nym_api_url: Option<Url>,
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should choose gateway.
/// If unspecified, the client will attempt to load the config from the storage.
pub(crate) gateway: Option<IdentityKey>,
pub(crate) debug: ConfigDebug,
pub(crate) packet_type: PacketType,
} }
#[wasm_bindgen] #[wasm_bindgen]
impl Config { impl Config {
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new( pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
id: String,
validator_server: String,
packet_type: Option<String>,
gateway: Option<IdentityKey>,
debug: Option<Debug>,
) -> Self {
let packet_type = if let Some(packet_type) = packet_type {
match packet_type.as_str() {
"outfox" => PacketType::Outfox,
_ => PacketType::Mix,
}
} else {
PacketType::Mix
};
Config { Config {
id, base: BaseClientConfig::new(id, env!("CARGO_PKG_VERSION").to_string())
nym_api_url: Some( .with_custom_nyxd(vec![validator_server
validator_server
.parse() .parse()
.expect("provided url was malformed"), .expect("provided url was malformed")])
), .with_debug_config(debug.map(Into::into).unwrap_or_default()),
disabled_credentials_mode: true, }
gateway, }
debug: debug.map(Into::into).unwrap_or_default(),
packet_type, 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),
} }
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Traffic { pub struct TrafficWasm {
/// The parameter of Poisson distribution determining how long, on average, /// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node. /// 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 /// So for a packet going through three mix nodes, on average, it will take three times this value
@@ -92,14 +68,23 @@ pub struct Traffic {
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. /// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool, 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 From<Traffic> for ConfigTraffic { impl From<TrafficWasm> for ConfigTraffic {
fn from(traffic: Traffic) -> Self { fn from(traffic: TrafficWasm) -> Self {
let use_extended_packet_size = traffic let use_extended_packet_size = traffic
.use_extended_packet_size .use_extended_packet_size
.then(|| PacketSize::ExtendedPacket32); .then(|| PacketSize::ExtendedPacket32);
let packet_type = if traffic.use_outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
ConfigTraffic { ConfigTraffic {
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms), average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
message_sending_average_delay: Duration::from_millis( message_sending_average_delay: Duration::from_millis(
@@ -109,27 +94,28 @@ impl From<Traffic> for ConfigTraffic {
.disable_main_poisson_packet_distribution, .disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket, primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size, secondary_packet_size: use_extended_packet_size,
packet_type: None, packet_type,
} }
} }
} }
impl From<ConfigTraffic> for Traffic { impl From<ConfigTraffic> for TrafficWasm {
fn from(traffic: ConfigTraffic) -> Self { fn from(traffic: ConfigTraffic) -> Self {
Traffic { TrafficWasm {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64, average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis() message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u64, as u64,
disable_main_poisson_packet_distribution: traffic disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution, .disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.secondary_packet_size.is_some(), use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
} }
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct CoverTraffic { pub struct CoverTrafficWasm {
/// The parameter of Poisson distribution determining how long, on average, /// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent. /// it is going to take for another loop cover traffic message to be sent.
pub loop_cover_traffic_average_delay_ms: u64, pub loop_cover_traffic_average_delay_ms: u64,
@@ -143,8 +129,8 @@ pub struct CoverTraffic {
pub disable_loop_cover_traffic_stream: bool, pub disable_loop_cover_traffic_stream: bool,
} }
impl From<CoverTraffic> for ConfigCoverTraffic { impl From<CoverTrafficWasm> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTraffic) -> Self { fn from(cover_traffic: CoverTrafficWasm) -> Self {
ConfigCoverTraffic { ConfigCoverTraffic {
loop_cover_traffic_average_delay: Duration::from_millis( loop_cover_traffic_average_delay: Duration::from_millis(
cover_traffic.loop_cover_traffic_average_delay_ms, cover_traffic.loop_cover_traffic_average_delay_ms,
@@ -155,9 +141,9 @@ impl From<CoverTraffic> for ConfigCoverTraffic {
} }
} }
impl From<ConfigCoverTraffic> for CoverTraffic { impl From<ConfigCoverTraffic> for CoverTrafficWasm {
fn from(cover_traffic: ConfigCoverTraffic) -> Self { fn from(cover_traffic: ConfigCoverTraffic) -> Self {
CoverTraffic { CoverTrafficWasm {
loop_cover_traffic_average_delay_ms: cover_traffic loop_cover_traffic_average_delay_ms: cover_traffic
.loop_cover_traffic_average_delay .loop_cover_traffic_average_delay
.as_millis() as u64, .as_millis() as u64,
@@ -169,14 +155,14 @@ impl From<ConfigCoverTraffic> for CoverTraffic {
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct GatewayConnection { pub struct GatewayConnectionWasm {
/// How long we're willing to wait for a response to a message sent to the gateway, /// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it. /// before giving up on it.
pub gateway_response_timeout_ms: u64, pub gateway_response_timeout_ms: u64,
} }
impl From<GatewayConnection> for ConfigGatewayConnection { impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnection) -> Self { fn from(gateway_connection: GatewayConnectionWasm) -> Self {
ConfigGatewayConnection { ConfigGatewayConnection {
gateway_response_timeout: Duration::from_millis( gateway_response_timeout: Duration::from_millis(
gateway_connection.gateway_response_timeout_ms, gateway_connection.gateway_response_timeout_ms,
@@ -185,9 +171,9 @@ impl From<GatewayConnection> for ConfigGatewayConnection {
} }
} }
impl From<ConfigGatewayConnection> for GatewayConnection { impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
fn from(gateway_connection: ConfigGatewayConnection) -> Self { fn from(gateway_connection: ConfigGatewayConnection) -> Self {
GatewayConnection { GatewayConnectionWasm {
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis() gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
as u64, as u64,
} }
@@ -196,7 +182,7 @@ impl From<ConfigGatewayConnection> for GatewayConnection {
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Acknowledgements { pub struct AcknowledgementsWasm {
/// The parameter of Poisson distribution determining how long, on average, /// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node. /// 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 /// So for an ack going through three mix nodes, on average, it will take three times this value
@@ -214,8 +200,8 @@ pub struct Acknowledgements {
pub ack_wait_addition_ms: u64, pub ack_wait_addition_ms: u64,
} }
impl From<Acknowledgements> for ConfigAcknowledgements { impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
fn from(acknowledgements: Acknowledgements) -> Self { fn from(acknowledgements: AcknowledgementsWasm) -> Self {
ConfigAcknowledgements { ConfigAcknowledgements {
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms), average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
ack_wait_multiplier: acknowledgements.ack_wait_multiplier, ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
@@ -224,9 +210,9 @@ impl From<Acknowledgements> for ConfigAcknowledgements {
} }
} }
impl From<ConfigAcknowledgements> for Acknowledgements { impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
fn from(acknowledgements: ConfigAcknowledgements) -> Self { fn from(acknowledgements: ConfigAcknowledgements) -> Self {
Acknowledgements { AcknowledgementsWasm {
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64, average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: acknowledgements.ack_wait_multiplier, ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64, ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
@@ -236,7 +222,7 @@ impl From<ConfigAcknowledgements> for Acknowledgements {
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Topology { pub struct TopologyWasm {
/// The uniform delay every which clients are querying the directory server /// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through. /// to try to obtain a compatible network topology to send sphinx packets through.
pub topology_refresh_rate_ms: u64, pub topology_refresh_rate_ms: u64,
@@ -252,8 +238,8 @@ pub struct Topology {
pub disable_refreshing: bool, pub disable_refreshing: bool,
} }
impl From<Topology> for ConfigTopology { impl From<TopologyWasm> for ConfigTopology {
fn from(topology: Topology) -> Self { fn from(topology: TopologyWasm) -> Self {
ConfigTopology { ConfigTopology {
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms), topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis( topology_resolution_timeout: Duration::from_millis(
@@ -264,9 +250,9 @@ impl From<Topology> for ConfigTopology {
} }
} }
impl From<ConfigTopology> for Topology { impl From<ConfigTopology> for TopologyWasm {
fn from(topology: ConfigTopology) -> Self { fn from(topology: ConfigTopology) -> Self {
Topology { TopologyWasm {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64, topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64, topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
disable_refreshing: topology.disable_refreshing, disable_refreshing: topology.disable_refreshing,
@@ -276,7 +262,7 @@ impl From<ConfigTopology> for Topology {
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ReplySurbs { pub struct ReplySurbsWasm {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. /// 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. /// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize, pub minimum_reply_surb_storage_threshold: usize,
@@ -310,8 +296,8 @@ pub struct ReplySurbs {
pub maximum_reply_key_age_ms: u64, pub maximum_reply_key_age_ms: u64,
} }
impl From<ReplySurbs> for ConfigReplySurbs { impl From<ReplySurbsWasm> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbs) -> Self { fn from(reply_surbs: ReplySurbsWasm) -> Self {
ConfigReplySurbs { ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
@@ -331,9 +317,9 @@ impl From<ReplySurbs> for ConfigReplySurbs {
} }
} }
impl From<ConfigReplySurbs> for ReplySurbs { impl From<ConfigReplySurbs> for ReplySurbsWasm {
fn from(reply_surbs: ConfigReplySurbs) -> Self { fn from(reply_surbs: ConfigReplySurbs) -> Self {
ReplySurbs { ReplySurbsWasm {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_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, minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
@@ -355,28 +341,28 @@ impl From<ConfigReplySurbs> for ReplySurbs {
// just a helper structure to more easily pass through the JS boundary // just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Debug { pub struct DebugWasm {
/// Defines all configuration options related to traffic streams. /// Defines all configuration options related to traffic streams.
pub traffic: Traffic, pub traffic: TrafficWasm,
/// Defines all configuration options related to cover traffic stream(s). /// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic, pub cover_traffic: CoverTrafficWasm,
/// Defines all configuration options related to the gateway connection. /// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection, pub gateway_connection: GatewayConnectionWasm,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements, pub acknowledgements: AcknowledgementsWasm,
/// Defines all configuration options related topology, such as refresh rates or timeouts. /// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology, pub topology: TopologyWasm,
/// Defines all configuration options related to reply SURBs. /// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs, pub reply_surbs: ReplySurbsWasm,
} }
impl From<Debug> for ConfigDebug { impl From<DebugWasm> for ConfigDebug {
fn from(debug: Debug) -> Self { fn from(debug: DebugWasm) -> Self {
ConfigDebug { ConfigDebug {
traffic: debug.traffic.into(), traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(), cover_traffic: debug.cover_traffic.into(),
@@ -388,9 +374,9 @@ impl From<Debug> for ConfigDebug {
} }
} }
impl From<ConfigDebug> for Debug { impl From<ConfigDebug> for DebugWasm {
fn from(debug: ConfigDebug) -> Self { fn from(debug: ConfigDebug) -> Self {
Debug { DebugWasm {
traffic: debug.traffic.into(), traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(), cover_traffic: debug.cover_traffic.into(),
gateway_connection: debug.gateway_connection.into(), gateway_connection: debug.gateway_connection.into(),
@@ -402,6 +388,6 @@ impl From<ConfigDebug> for Debug {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn default_debug() -> Debug { pub fn default_debug() -> DebugWasm {
ConfigDebug::default().into() ConfigDebug::default().into()
} }
+50 -91
View File
@@ -7,7 +7,7 @@ use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID; use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError; use crate::error::WasmClientError;
use crate::helpers::{ use crate::helpers::{
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag, parse_recipient, parse_sender_tag, setup_from_topology, setup_gateway_from_api,
setup_reply_surb_storage_backend, setup_reply_surb_storage_backend,
}; };
use crate::storage::traits::FullWasmClientStorage; use crate::storage::traits::FullWasmClientStorage;
@@ -15,14 +15,11 @@ use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology; use crate::topology::WasmNymTopology;
use js_sys::Promise; use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient}; use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{ use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
}; };
use nym_client_core::client::inbound_messages::InputMessage; use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::replies::reply_storage::browser_backend; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_client_core::config::{CoverTraffic, DebugConfig, Topology, Traffic};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::params::PacketType; use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use nym_task::TaskManager; use nym_task::TaskManager;
@@ -30,7 +27,7 @@ use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology; use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey; use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::{thread_rng, RngCore}; use rand::RngCore;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures::future_to_promise;
@@ -53,24 +50,18 @@ pub struct NymClient {
// even though we don't use graceful shutdowns, other components rely on existence of this struct // even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline // and if it's dropped, everything will start going offline
_task_manager: TaskManager, _task_manager: TaskManager,
packet_type: Option<PacketType>,
packet_type: PacketType,
} }
#[wasm_bindgen] #[wasm_bindgen]
pub struct NymClientBuilder { pub struct NymClientBuilder {
config: Config, config: Config,
custom_topology: Option<NymTopology>, custom_topology: Option<NymTopology>,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>, storage_passphrase: Option<String>,
reply_surb_storage_backend: browser_backend::Backend,
on_message: js_sys::Function, on_message: js_sys::Function,
// unimplemented:
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
packet_type: Option<PacketType>,
} }
#[wasm_bindgen] #[wasm_bindgen]
@@ -79,17 +70,15 @@ impl NymClientBuilder {
pub fn new( pub fn new(
config: Config, config: Config,
on_message: js_sys::Function, on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>, storage_passphrase: Option<String>,
) -> Self { ) -> Self {
NymClientBuilder { NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
config, config,
custom_topology: None, custom_topology: None,
storage_passphrase, storage_passphrase,
on_message, on_message,
bandwidth_controller: None, preferred_gateway,
disabled_credentials: true,
packet_type: None,
} }
} }
@@ -108,40 +97,14 @@ impl NymClientBuilder {
} }
} }
let full_config = Config { let full_config = Config::new_tester_config(NODE_TESTER_CLIENT_ID);
id: NODE_TESTER_CLIENT_ID.to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway,
debug: DebugConfig {
traffic: Traffic {
disable_main_poisson_packet_distribution: true,
..Default::default()
},
cover_traffic: CoverTraffic {
disable_loop_cover_traffic_stream: true,
..Default::default()
},
topology: Topology {
disable_refreshing: true,
..Default::default()
},
..Default::default()
},
packet_type: PacketType::Mix,
};
NymClientBuilder { NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(
full_config.debug.reply_surbs,
),
config: full_config, config: full_config,
custom_topology: Some(topology.into()), custom_topology: Some(topology.into()),
on_message, on_message,
bandwidth_controller: None,
disabled_credentials: true,
storage_passphrase: None, storage_passphrase: None,
packet_type: None, preferred_gateway: gateway,
} }
} }
@@ -157,59 +120,48 @@ impl NymClientBuilder {
} }
} }
fn initialise_storage(config: &Config, base_storage: ClientStorage) -> FullWasmClientStorage {
FullWasmClientStorage {
keys_and_gateway_store: base_storage,
reply_storage: setup_reply_surb_storage_backend(config.base.debug.reply_surbs),
credential_storage: EphemeralCredentialStorage::default(),
}
}
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> { async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client"); console_log!("Starting the wasm client");
let disabled_credentials = if self.disabled_credentials { let nym_api_endpoints = self.config.base.client.nym_api_urls.clone();
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let nym_api_endpoints = match &self.config.nym_api_url {
Some(endpoint) => vec![endpoint.clone()],
None => Vec::new(),
};
// TODO: this will have to be re-used for surbs. but this is a problem for another PR. // TODO: this will have to be re-used for surbs. but this is a problem for another PR.
let client_store = let client_store =
ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?; ClientStorage::new_async(&self.config.base.client.id, self.storage_passphrase.take())
.await?;
let user_chosen = self.preferred_gateway.clone();
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way // if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
let gateway_endpoint = if let Some(topology) = &self.custom_topology { if let Some(topology) = &self.custom_topology {
gateway_from_topology( setup_from_topology(user_chosen, topology, &client_store).await?
&mut thread_rng(),
self.config.gateway.as_deref(),
topology,
&client_store,
)
.await?
} else { } else {
choose_gateway( setup_gateway_from_api(&client_store, user_chosen, &nym_api_endpoints).await?
&client_store,
self.config.gateway.clone(),
&nym_api_endpoints,
)
.await?
}; };
let packet_type = self.config.base.debug.traffic.packet_type;
let storage = Self::initialise_storage(&self.config, client_store);
let maybe_topology_provider = self.topology_provider(); let maybe_topology_provider = self.topology_provider();
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new( let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> =
&gateway_endpoint, BaseClientBuilder::<FakeClient<DirectSigningNyxdClient>, _>::new(
&self.config.debug, &self.config.base,
client_store, storage,
self.bandwidth_controller, None,
self.reply_surb_storage_backend, );
disabled_credentials,
nym_api_endpoints,
);
if let Some(topology_provider) = maybe_topology_provider { if let Some(topology_provider) = maybe_topology_provider {
base_builder = base_builder.with_topology_provider(topology_provider); base_builder = base_builder.with_topology_provider(topology_provider);
} }
let packet_type = self.config.packet_type; let mut started_client = base_builder.start_base().await?;
let mut started_client = base_builder.start_base(packet_type).await?;
let self_address = started_client.address.to_string(); let self_address = started_client.address.to_string();
let client_input = started_client.client_input.register_producer(); let client_input = started_client.client_input.register_producer();
@@ -223,7 +175,7 @@ impl NymClientBuilder {
client_state: Arc::new(started_client.client_state), client_state: Arc::new(started_client.client_state),
_full_topology: None, _full_topology: None,
_task_manager: started_client.task_manager, _task_manager: started_client.task_manager,
packet_type: self.packet_type, packet_type,
}) })
} }
@@ -237,9 +189,10 @@ impl NymClient {
async fn _new( async fn _new(
config: Config, config: Config,
on_message: js_sys::Function, on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>, storage_passphrase: Option<String>,
) -> Result<NymClient, WasmClientError> { ) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message, storage_passphrase) NymClientBuilder::new(config, on_message, preferred_gateway, storage_passphrase)
.start_client_async() .start_client_async()
.await .await
} }
@@ -249,10 +202,11 @@ impl NymClient {
pub fn new( pub fn new(
config: Config, config: Config,
on_message: js_sys::Function, on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>, storage_passphrase: Option<String>,
) -> Promise { ) -> Promise {
future_to_promise(async move { future_to_promise(async move {
Self::_new(config, on_message, storage_passphrase) Self::_new(config, on_message, preferred_gateway, storage_passphrase)
.await .await
.into_promise_result() .into_promise_result()
}) })
@@ -319,7 +273,7 @@ impl NymClient {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type); let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
@@ -346,8 +300,13 @@ impl NymClient {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = let input_msg = InputMessage::new_anonymous(
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type); recipient,
message,
reply_surbs,
lane,
Some(self.packet_type),
);
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
@@ -365,7 +324,7 @@ impl NymClient {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type); let input_msg = InputMessage::new_reply(sender_tag, message, lane, Some(self.packet_type));
self.client_input.send_message(input_msg) self.client_input.send_message(input_msg)
} }
} }
+33 -84
View File
@@ -7,22 +7,21 @@ use crate::topology::WasmNymTopology;
use js_sys::Promise; use js_sys::Promise;
use nym_client_core::client::replies::reply_storage::browser_backend; use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config; use nym_client_core::config;
use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::GatewaySetup; use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationDetails};
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology; use nym_topology::{gateway, NymTopology};
use nym_validator_client::client::{IdentityKey, IdentityKeyRef}; use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient; use nym_validator_client::NymApiClient;
use rand::{CryptoRng, Rng}; use rand::thread_rng;
use url::Url; use url::Url;
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult}; use wasm_utils::PromisableResult;
// don't get too excited about the name, under the hood it's just a big fat placeholder // don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence // with no disk_persistence
pub(crate) fn setup_reply_surb_storage_backend( pub(crate) fn setup_reply_surb_storage_backend(
config: config::ReplySurbs, config: config::ReplySurbs,
) -> browser_backend::Backend { ) -> browser_backend::Backend {
@@ -79,87 +78,37 @@ pub fn current_network_topology(nym_api_url: String) -> Promise {
}) })
} }
pub(crate) async fn choose_gateway( async fn setup_gateway(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
gateways: &[gateway::Node],
) -> Result<InitialisationDetails, WasmClientError> {
let setup = if client_store.has_full_gateway_info().await? {
GatewaySetup::MustLoad
} else {
GatewaySetup::new_fresh(chosen_gateway.clone(), None)
};
setup_gateway_from(&setup, client_store, client_store, false, Some(gateways))
.await
.map_err(Into::into)
}
pub(crate) async fn setup_gateway_from_api(
client_store: &ClientStorage, client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>, chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url], nym_apis: &[Url],
) -> Result<GatewayEndpointConfig, WasmClientError> { ) -> Result<InitialisationDetails, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?; let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, nym_apis).await?;
console_log!("loaded: {:?}", existing_gateway_config); setup_gateway(client_store, chosen_gateway, &gateways).await
if let Some(existing) = existing_gateway_config {
if let Some(provided) = &chosen_gateway {
if provided != &existing.gateway_id {
return Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
});
}
}
return Ok(existing);
};
// if NOTHING is specified nor available, choose gateway randomly.
let setup = GatewaySetup::new(None, chosen_gateway, None);
let config = setup.try_get_gateway_details(nym_apis).await?;
// perform registration + persist the new gateway info
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
// the shared key yet. this will only happen when we start the base client itself.
// but unfortunately, we can't do much more until we do a bit more refactoring.
client_store.store_gateway_config(&config).await?;
console_log!("stored: {:?}", config);
Ok(config)
} }
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>( pub(crate) async fn setup_from_topology(
rng: &mut R, explicit_gateway: Option<IdentityKey>,
explicit_gateway: Option<IdentityKeyRef<'_>>,
topology: &NymTopology, topology: &NymTopology,
client_store: &ClientStorage, client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> { ) -> Result<InitialisationDetails, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?; let gateways = topology.gateways();
console_log!("loaded: {:?}", existing_gateway_config); setup_gateway(client_store, explicit_gateway, gateways).await
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
if let Some(existing) = existing_gateway_config {
// we have stored gateway info and explicitly provided identity key
//
// check if they match, otherwise return an error
return if provided != existing.gateway_id {
Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
})
} else {
Ok(existing)
};
} else {
// we have explicitly provided identity key and didn't have any prior stored data
//
// try to grab details from the topology
let gateway_identity = identity::PublicKey::from_base58_string(provided)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
gateway.clone().into()
} else {
return Err(WasmClientError::NonExistentGateway {
gateway_identity: gateway_identity.to_base58_string(),
});
}
}
} else if let Some(existing) = existing_gateway_config {
// we have stored data and didn't provide anything separately - use what's stored!
return Ok(existing);
} else {
// we don't have anything stored nor we have provided anything
//
// just grab random gateway from our topology
topology.random_gateway(rng)?.clone().into()
};
console_log!("storing: {:?}", new_gateway);
client_store.store_gateway_config(&new_gateway).await?;
Ok(new_gateway)
} }
@@ -16,6 +16,9 @@ pub enum ClientStorageError {
#[error("{typ} cryptographic key is not available in storage")] #[error("{typ} cryptographic key is not available in storage")]
CryptoKeyNotInStorage { typ: String }, CryptoKeyNotInStorage { typ: String },
#[error("the prior gateway details are not available in the storage")]
GatewayDetailsNotInStorage,
} }
impl From<ClientStorageError> for JsValue { impl From<ClientStorageError> for JsValue {
+44 -10
View File
@@ -1,9 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::storage::errors::ClientStorageError; use crate::storage::errors::ClientStorageError;
use js_sys::Promise; use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
use nym_crypto::asymmetric::{encryption, identity}; use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys; use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey; use nym_sphinx::acknowledgements::AckKey;
@@ -27,8 +28,8 @@ mod v1 {
pub const CORE_STORE: &str = "core"; pub const CORE_STORE: &str = "core";
// keys // keys
// TODO: to replace with FULL config pub const CONFIG: &str = "config";
pub const GATEWAY_CONFIG: &str = "gateway_config"; pub const GATEWAY_DETAILS: &str = "gateway_details";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair"; pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair"; pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
@@ -110,15 +111,32 @@ impl ClientStorage {
}) })
} }
pub(crate) async fn read_gateway_config( // TODO: persist client's config
&self, #[allow(dead_code)]
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> { pub(crate) async fn read_config(&self) -> Result<Option<Config>, ClientStorageError> {
self.inner self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG)) .read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG))
.await .await
.map_err(Into::into) .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( async fn may_read_identity_keypair(
&self, &self,
) -> Result<Option<identity::KeyPair>, ClientStorageError> { ) -> Result<Option<identity::KeyPair>, ClientStorageError> {
@@ -244,17 +262,33 @@ impl ClientStorage {
.map_err(Into::into) .map_err(Into::into)
} }
pub(crate) async fn store_gateway_config( pub(crate) async fn store_gateway_details(
&self, &self,
gateway_endpoint: &GatewayEndpointConfig, gateway_endpoint: &PersistedGatewayDetails,
) -> Result<(), ClientStorageError> { ) -> Result<(), ClientStorageError> {
self.inner self.inner
.store_value( .store_value(
v1::CORE_STORE, v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_CONFIG), JsValue::from_str(v1::GATEWAY_DETAILS),
gateway_endpoint, gateway_endpoint,
) )
.await .await
.map_err(Into::into) .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)
}
} }
+31 -6
View File
@@ -4,6 +4,9 @@
use crate::storage::errors::ClientStorageError; use crate::storage::errors::ClientStorageError;
use crate::storage::ClientStorage; use crate::storage::ClientStorage;
use async_trait::async_trait; use async_trait::async_trait;
use nym_client_core::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedGatewayDetails,
};
use nym_client_core::client::base_client::storage::MixnetClientStorage; use nym_client_core::client::base_client::storage::MixnetClientStorage;
use nym_client_core::client::key_manager::persistence::KeyStore; use nym_client_core::client::key_manager::persistence::KeyStore;
use nym_client_core::client::key_manager::KeyManager; use nym_client_core::client::key_manager::KeyManager;
@@ -14,9 +17,9 @@ use wasm_utils::console_log;
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage` // temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
// implementing all traits and everything getting combined // implementing all traits and everything getting combined
pub struct FullWasmClientStorage { pub struct FullWasmClientStorage {
key_store: ClientStorage, pub(crate) keys_and_gateway_store: ClientStorage,
reply_storage: browser_backend::Backend, pub(crate) reply_storage: browser_backend::Backend,
credential_storage: EphemeralCredentialStorage, pub(crate) credential_storage: EphemeralCredentialStorage,
} }
impl MixnetClientStorage for FullWasmClientStorage { impl MixnetClientStorage for FullWasmClientStorage {
@@ -24,12 +27,14 @@ impl MixnetClientStorage for FullWasmClientStorage {
type ReplyStore = browser_backend::Backend; type ReplyStore = browser_backend::Backend;
type CredentialStore = EphemeralCredentialStorage; type CredentialStore = EphemeralCredentialStorage;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { type GatewayDetailsStore = ClientStorage;
(self.key_store, self.reply_storage, self.credential_storage)
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_storage, self.credential_storage)
} }
fn key_store(&self) -> &Self::KeyStore { fn key_store(&self) -> &Self::KeyStore {
&self.key_store &self.keys_and_gateway_store
} }
fn reply_store(&self) -> &Self::ReplyStore { fn reply_store(&self) -> &Self::ReplyStore {
@@ -39,6 +44,10 @@ impl MixnetClientStorage for FullWasmClientStorage {
fn credential_store(&self) -> &Self::CredentialStore { fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_storage &self.credential_storage
} }
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.keys_and_gateway_store
}
} }
#[async_trait(?Send)] #[async_trait(?Send)]
@@ -74,3 +83,19 @@ impl KeyStore for ClientStorage {
.await .await
} }
} }
#[async_trait(?Send)]
impl GatewayDetailsStore for ClientStorage {
type StorageError = ClientStorageError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.must_read_gateway_details().await
}
async fn store_gateway_details(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
self.store_gateway_details(details).await
}
}
+15 -22
View File
@@ -3,7 +3,7 @@
use crate::constants::NODE_TESTER_ID; use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError; use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, gateway_from_topology}; use crate::helpers::{current_network_topology_async, setup_from_topology};
use crate::storage::ClientStorage; use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver; use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{ use crate::tester::helpers::{
@@ -15,7 +15,7 @@ use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient}; use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController; use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::ManagedKeys; use nym_client_core::client::key_manager::ManagedKeys;
use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::init::InitialisationDetails;
use nym_credential_storage::ephemeral_storage::EphemeralStorage; use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_gateway_client::GatewayClient; use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver; use nym_node_tester_utils::receiver::SimpleMessageReceiver;
@@ -28,7 +28,6 @@ use nym_task::TaskManager;
use nym_topology::NymTopology; use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey; use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::{CryptoRng, Rng};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex}; use std::sync::{Arc, Mutex as SyncMutex};
@@ -120,29 +119,26 @@ impl NymNodeTesterBuilder {
}) })
} }
async fn gateway_info<R: Rng + CryptoRng>( async fn gateway_info(
&self, &self,
rng: &mut R,
client_store: &ClientStorage, client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> { ) -> Result<InitialisationDetails, WasmClientError> {
gateway_from_topology( if let Ok(loaded) = InitialisationDetails::try_load(client_store, client_store).await {
rng, Ok(loaded)
self.gateway.as_deref(), } else {
&self.base_topology, setup_from_topology(self.gateway.clone(), &self.base_topology, client_store).await
client_store, }
)
.await
} }
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> { async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let mut rng = OsRng;
let task_manager = TaskManager::default(); let task_manager = TaskManager::default();
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?; let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?; let init_details = self.gateway_info(&client_store).await?;
let gateway_endpoint = init_details.gateway_details;
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?; let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await; let managed_keys = init_details.managed_keys;
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded(); let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -151,7 +147,7 @@ impl NymNodeTesterBuilder {
gateway_endpoint.gateway_listener, gateway_endpoint.gateway_listener,
managed_keys.identity_keypair(), managed_keys.identity_keypair(),
gateway_identity, gateway_identity,
managed_keys.gateway_shared_key(), Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender, mixnet_message_sender,
ack_sender, ack_sender,
Duration::from_secs(10), Duration::from_secs(10),
@@ -160,14 +156,11 @@ impl NymNodeTesterBuilder {
); );
gateway_client.set_disabled_credentials_mode(true); gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?; gateway_client.authenticate_and_start().await?;
managed_keys
.deal_with_gateway_key(shared_keys, &client_store)
.await?;
// TODO: make those values configurable later // TODO: make those values configurable later
let tester = NodeTester::new( let tester = NodeTester::new(
rng, OsRng,
self.base_topology, self.base_topology,
Some(address(&managed_keys, gateway_identity)), Some(address(&managed_keys, gateway_identity)),
PacketSize::default(), PacketSize::default(),
+17 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-bin-common" name = "nym-bin-common"
version = "0.5.0" version = "0.6.0"
description = "Common code for nym binaries" description = "Common code for nym binaries"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -15,15 +15,21 @@ clap_complete_fig = "4.0"
log = { workspace = true } log = { workspace = true }
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
semver = "0.11" semver = "0.11"
serde = { workspace = true, features = ["derive"], optional = true } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true } serde_json = { workspace = true, optional = true }
## tracing ## tracing
tracing-appender = { version = "0.2.2", optional = true }
tracing-subscriber = { version = "0.3.16", features = [ tracing-subscriber = { version = "0.3.16", features = [
"env-filter", "env-filter",
], optional = true } ], optional = true }
tracing-tree = { version = "0.2.2", optional = true } tracing-tree = { version = "0.2.2", optional = true }
opentelemetry-jaeger = { version = "0.18.0", optional = true, features = [
"rt-tokio",
"collector_client",
"isahc_collector_client",
] }
tracing-opentelemetry = { version = "0.19.0", optional = true }
opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
[build-dependencies] [build-dependencies]
@@ -36,5 +42,11 @@ vergen = { version = "=7.4.3", default-features = false, features = [
[features] [features]
default = [] default = []
output_format = ["serde", "serde_json"] output_format = ["serde_json"]
tracing = ["tracing-appender", "tracing-subscriber", "tracing-tree"] tracing = [
"tracing-subscriber",
"tracing-tree",
"opentelemetry-jaeger",
"tracing-opentelemetry",
"opentelemetry",
]
@@ -4,6 +4,8 @@
// TODO: at a later date this crate should probably also expose `ContractBuildInformation` // TODO: at a later date this crate should probably also expose `ContractBuildInformation`
// and be used by our smart contracts // and be used by our smart contracts
use serde::{Deserialize, Serialize};
#[derive(Debug)] #[derive(Debug)]
pub struct BinaryBuildInformation { pub struct BinaryBuildInformation {
// VERGEN_BUILD_TIMESTAMP // VERGEN_BUILD_TIMESTAMP
@@ -99,8 +101,7 @@ impl BinaryBuildInformation {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BinaryBuildInformationOwned { pub struct BinaryBuildInformationOwned {
// VERGEN_BUILD_TIMESTAMP // VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
+3 -1
View File
@@ -4,5 +4,7 @@
pub mod build_information; pub mod build_information;
pub mod completions; pub mod completions;
pub mod logging; pub mod logging;
pub mod output_format;
pub mod version_checker; pub mod version_checker;
#[cfg(feature = "output_format")]
pub mod output_format;
+34 -18
View File
@@ -1,17 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// use tracing_subscriber::{
// fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry, use serde::{Deserialize, Serialize};
// };
// use tracing_tree::HierarchicalLayer;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub use tracing_appender; pub use opentelemetry;
#[cfg(feature = "tracing")]
pub use opentelemetry_jaeger;
#[cfg(feature = "tracing")]
pub use tracing_opentelemetry;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub use tracing_subscriber; pub use tracing_subscriber;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub use tracing_tree; pub use tracing_tree;
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettings {
// well, we need to implement something here at some point...
}
// I'd argue we should start transitioning from `log` to `tracing` // I'd argue we should start transitioning from `log` to `tracing`
pub fn setup_logging() { pub fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder(); let mut log_builder = pretty_env_logger::formatted_timed_builder();
@@ -39,27 +47,35 @@ pub fn setup_logging() {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
#[macro_export] #[macro_export]
macro_rules! setup_tracing { macro_rules! setup_tracing {
($file_name: expr) => { ($service_name: expr) => {
use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt; use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt;
use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt; use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt;
let file_appender = let registry = nym_bin_common::logging::tracing_subscriber::Registry::default()
nym_bin_common::logging::tracing_appender::rolling::hourly($file_name, "log");
let (non_blocking, _guard) =
nym_bin_common::logging::tracing_appender::non_blocking(file_appender);
let appender_layer = nym_bin_common::logging::tracing_subscriber::fmt::Layer::new()
.with_ansi(false)
.with_writer(non_blocking);
nym_bin_common::logging::tracing_subscriber::Registry::default()
.with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env()) .with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env())
.with(appender_layer)
.with( .with(
nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4) nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4)
.with_targets(true) .with_targets(true)
.with_bracketed_fields(true), .with_bracketed_fields(true),
);
let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline()
.with_endpoint("http://44.199.230.10:14268/api/traces")
.with_service_name($service_name)
.with_isahc()
.with_trace_config(
nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler(
nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased(
0.1,
),
),
) )
.init(); .install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio)
.expect("Could not init tracer");
let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer);
registry.with(telemetry).init();
}; };
} }
+3 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client-core" name = "nym-client-core"
version = "1.1.14" version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
rust-version = "1.66" rust-version = "1.66"
@@ -9,6 +9,7 @@ rust-version = "1.66"
[dependencies] [dependencies]
async-trait = { workspace = true } async-trait = { workspace = true }
base64 = "0.21.2"
dirs = "4.0" dirs = "4.0"
dashmap = "5.4.0" dashmap = "5.4.0"
futures = "0.3" futures = "0.3"
@@ -17,6 +18,7 @@ log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
sha2 = "0.10.6"
tap = "1.0.1" tap = "1.0.1"
thiserror = "1.0.34" thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] } url = { version ="2.2", features = ["serde"] }
@@ -42,7 +42,6 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths}; use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager}; use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider; use nym_topology::provider_trait::TopologyProvider;
use rand::rngs::OsRng;
use std::sync::Arc; use std::sync::Arc;
use tap::TapFallible; use tap::TapFallible;
use url::Url; use url::Url;
@@ -50,6 +49,8 @@ use url::Url;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient; use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
use crate::client::base_client::storage::gateway_details::GatewayDetailsStore;
use crate::init::{setup_gateway, GatewaySetup, InitialisationDetails};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient; use nym_validator_client::nyxd::traits::DkgQueryClient;
@@ -157,17 +158,11 @@ impl From<bool> for CredentialsToggle {
} }
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> { pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
// due to wasm limitations I had to split it like this : ( config: &'a Config,
gateway_config: &'a GatewayEndpointConfig, client_store: S,
debug_config: &'a DebugConfig, dkg_query_client: Option<C>,
disabled_credentials: bool,
nym_api_endpoints: Vec<Url>,
reply_storage_backend: S::ReplyStore,
key_store: S::KeyStore,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>, custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>, setup_method: GatewaySetup,
managed_keys: ManagedKeys,
} }
impl<'a, C, S> BaseClientBuilder<'a, C, S> impl<'a, C, S> BaseClientBuilder<'a, C, S>
@@ -175,47 +170,23 @@ where
S: MixnetClientStorage + 'static, S: MixnetClientStorage + 'static,
C: DkgQueryClient + Send + Sync + 'static, C: DkgQueryClient + Send + Sync + 'static,
{ {
// TODO: combine all storages pub fn new(
pub fn new_from_base_config<T>( base_config: &'a Config,
base_config: &'a Config<T>, client_store: S,
key_store: S::KeyStore, dkg_query_client: Option<C>,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
) -> BaseClientBuilder<'a, C, S> { ) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder { BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(), config: base_config,
debug_config: base_config.get_debug_config(), client_store,
disabled_credentials: base_config.get_disabled_credentials_mode(), dkg_query_client,
nym_api_endpoints: base_config.get_nym_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_store,
managed_keys: ManagedKeys::Invalidated,
custom_topology_provider: None, custom_topology_provider: None,
setup_method: GatewaySetup::MustLoad,
} }
} }
// TODO: combine all storages pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
pub fn new( self.setup_method = setup;
gateway_config: &'a GatewayEndpointConfig, self
debug_config: &'a DebugConfig,
key_store: S::KeyStore,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_store,
managed_keys: ManagedKeys::Invalidated,
}
} }
pub fn with_topology_provider( pub fn with_topology_provider(
@@ -228,13 +199,16 @@ where
// note: do **NOT** make this method public as its only valid usage is from within `start_base` // note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded // because it relies on the crypto keys being already loaded
fn as_mix_recipient(&self) -> Recipient { fn mix_address(
managed_keys: &ManagedKeys,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new( Recipient::new(
*self.managed_keys.identity_public_key(), *managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(), *managed_keys.encryption_public_key(),
// TODO: below only works under assumption that gateway address == gateway id // TODO: below only works under assumption that gateway address == gateway id
// (which currently is true) // (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(), NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(),
) )
} }
@@ -318,7 +292,10 @@ where
} }
async fn start_gateway_client( async fn start_gateway_client(
&mut self, config: &Config,
gateway_config: GatewayEndpointConfig,
managed_keys: &ManagedKeys,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
mixnet_message_sender: MixnetMessageSender, mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender, ack_sender: AcknowledgementSender,
shutdown: TaskClient, shutdown: TaskClient,
@@ -327,33 +304,26 @@ where
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static, <S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static, <S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{ {
let gateway_id = self.gateway_config.gateway_id.clone(); let gateway_address = gateway_config.gateway_listener.clone();
if gateway_id.is_empty() { let gateway_id = gateway_config.gateway_id;
return Err(ClientCoreError::GatewayIdUnknown);
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
return Err(ClientCoreError::GatewayAddressUnknown);
}
// TODO: in theory, at this point, this should be infallible
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut gateway_client = GatewayClient::new( let mut gateway_client = GatewayClient::new(
gateway_address, gateway_address,
self.managed_keys.identity_keypair(), managed_keys.identity_keypair(),
gateway_identity, gateway_identity,
self.managed_keys.gateway_shared_key(), Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender, mixnet_message_sender,
ack_sender, ack_sender,
self.debug_config config.debug.gateway_connection.gateway_response_timeout,
.gateway_connection bandwidth_controller,
.gateway_response_timeout,
self.bandwidth_controller.take(),
shutdown, shutdown,
); );
gateway_client.set_disabled_credentials_mode(self.disabled_credentials); gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
let shared_key = gateway_client let shared_key = gateway_client
.authenticate_and_start() .authenticate_and_start()
@@ -362,12 +332,7 @@ where
log::error!("Could not authenticate and start up the gateway connection - {err}") log::error!("Could not authenticate and start up the gateway connection - {err}")
})?; })?;
self.managed_keys managed_keys.ensure_gateway_key(shared_key);
.deal_with_gateway_key(shared_key, &self.key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway_client) Ok(gateway_client)
} }
@@ -473,24 +438,41 @@ where
Ok(mem_store) Ok(mem_store)
} }
async fn initial_key_setup(&mut self) { async fn initialise_keys_and_gateway(&self) -> Result<InitialisationDetails, ClientCoreError>
assert!(!self.managed_keys.is_valid()); where
let mut rng = OsRng; <S::KeyStore as KeyStore>::StorageError: Sync + Send,
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await; <S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
{
setup_gateway(
&self.setup_method,
self.client_store.key_store(),
self.client_store.gateway_details_store(),
false,
Some(&self.config.client.nym_api_urls),
)
.await
} }
pub async fn start_base( pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
mut self,
packet_type: PacketType,
) -> Result<BaseClient, ClientCoreError>
where where
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync, S::ReplyStore: Send + Sync,
<S::KeyStore as KeyStore>::StorageError: Send + Sync, <S::KeyStore as KeyStore>::StorageError: Send + Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static, <S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
{ {
info!("Starting nym client"); info!("Starting nym client");
self.initial_key_setup().await;
// derive (or load) client keys and gateway configuration
let details = self.initialise_keys_and_gateway().await?;
let gateway_config = details.gateway_details;
let managed_keys = details.managed_keys;
let (reply_storage_backend, credential_store) = self.client_store.into_runtime_stores();
let bandwidth_controller = self
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // TODO: make the channels be internally created by the relevant components
@@ -518,34 +500,39 @@ where
let (reply_controller_sender, reply_controller_receiver) = let (reply_controller_sender, reply_controller_receiver) =
reply_controller::requests::new_control_channels(); reply_controller::requests::new_control_channels();
let self_address = self.as_mix_recipient(); let self_address = Self::mix_address(&managed_keys, &gateway_config);
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
let gateway_client = self let gateway_client = Self::start_gateway_client(
.start_gateway_client(mixnet_messages_sender, ack_sender, task_manager.subscribe()) self.config,
.await?; gateway_config,
&managed_keys,
let reply_storage = Self::setup_persistent_reply_storage( bandwidth_controller,
self.reply_storage_backend, mixnet_messages_sender,
ack_sender,
task_manager.subscribe(), task_manager.subscribe(),
) )
.await?; .await?;
let reply_storage =
Self::setup_persistent_reply_storage(reply_storage_backend, task_manager.subscribe())
.await?;
let topology_provider = Self::setup_topology_provider( let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(), self.custom_topology_provider.take(),
self.nym_api_endpoints, self.config.get_nym_api_endpoints(),
); );
Self::start_topology_refresher( Self::start_topology_refresher(
topology_provider, topology_provider,
self.debug_config.topology, self.config.debug.topology,
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
task_manager.subscribe(), task_manager.subscribe(),
) )
.await?; .await?;
Self::start_received_messages_buffer_controller( Self::start_received_messages_buffer_controller(
self.managed_keys.encryption_keypair(), managed_keys.encryption_keypair(),
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
reply_storage.key_storage(), reply_storage.key_storage(),
@@ -569,8 +556,8 @@ where
let shared_lane_queue_lengths = LaneQueueLengths::new(); let shared_lane_queue_lengths = LaneQueueLengths::new();
let controller_config = real_messages_control::Config::new( let controller_config = real_messages_control::Config::new(
self.debug_config, &self.config.debug,
self.managed_keys.ack_key(), managed_keys.ack_key(),
self_address, self_address,
); );
@@ -586,17 +573,18 @@ where
shared_lane_queue_lengths.clone(), shared_lane_queue_lengths.clone(),
client_connection_rx, client_connection_rx,
task_manager.subscribe(), task_manager.subscribe(),
packet_type, self.config.debug.traffic.packet_type,
); );
if !self if !self
.debug_config .config
.debug
.cover_traffic .cover_traffic
.disable_loop_cover_traffic_stream .disable_loop_cover_traffic_stream
{ {
Self::start_cover_traffic_stream( Self::start_cover_traffic_stream(
self.debug_config, &self.config.debug,
self.managed_keys.ack_key(), managed_keys.ack_key(),
self_address, self_address,
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
message_sender, message_sender,
@@ -101,8 +101,8 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
} }
} }
pub fn create_bandwidth_controller<T, St: CredentialStorage>( pub fn create_bandwidth_controller<St: CredentialStorage>(
config: &Config<T>, config: &Config,
storage: St, storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> { ) -> BandwidthController<Client<QueryNyxdClient>, St> {
let nyxd_url = config let nyxd_url = config
@@ -122,13 +122,30 @@ pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nym_api_url: Url, nym_api_url: Url,
storage: St, storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> { ) -> BandwidthController<Client<QueryNyxdClient>, St> {
let client = default_query_dkg_client(nyxd_url, nym_api_url);
BandwidthController::new(storage, client)
}
pub fn default_query_dkg_client_from_config(config: &Config) -> Client<QueryNyxdClient> {
let nyxd_url = config
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
default_query_dkg_client(nyxd_url, api_url)
}
pub fn default_query_dkg_client(nyxd_url: Url, nym_api_url: Url) -> Client<QueryNyxdClient> {
let details = nym_network_defaults::NymNetworkDetails::new_from_env(); let details = nym_network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details) let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config"); .expect("failed to construct validator client config");
// overwrite env configuration with config URLs // overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, nym_api_url); client_config = client_config.with_urls(nyxd_url, nym_api_url);
let client = nym_validator_client::Client::new_query(client_config) nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client"); .expect("Could not construct query client")
BandwidthController::new(storage, client)
} }
@@ -0,0 +1,201 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use async_trait::async_trait;
use nym_gateway_requests::registration::handshake::SharedKeys;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::error::Error;
use std::ops::Deref;
use tokio::sync::Mutex;
use zeroize::Zeroizing;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait GatewayDetailsStore {
type StorageError: Error;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError>;
async fn store_gateway_details(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedGatewayDetails {
// TODO: should we also verify correctness of the details themselves?
// i.e. we could include a checksum or tag (via the shared keys)
// counterargument: if we wanted to modify, say, the host information in the stored file on disk,
// in order to actually use it, we'd have to recompute the whole checksum which would be a huge pain.
/// The hash of the shared keys to ensure the correct ones are used with those gateway details.
#[serde(with = "base64")]
key_hash: Vec<u8>,
/// Actual gateway details being persisted.
pub(crate) details: GatewayEndpointConfig,
}
impl From<PersistedGatewayDetails> for GatewayEndpointConfig {
fn from(value: PersistedGatewayDetails) -> Self {
value.details
}
}
impl PersistedGatewayDetails {
pub fn new(details: GatewayEndpointConfig, shared_key: &SharedKeys) -> Self {
let key_bytes = Zeroizing::new(shared_key.to_bytes());
let mut key_hasher = Sha256::new();
key_hasher.update(&key_bytes);
let key_hash = key_hasher.finalize().to_vec();
PersistedGatewayDetails { key_hash, details }
}
pub fn verify(&self, shared_key: &SharedKeys) -> bool {
let key_bytes = Zeroizing::new(shared_key.to_bytes());
let mut key_hasher = Sha256::new();
key_hasher.update(&key_bytes);
let key_hash = key_hasher.finalize();
self.key_hash == key_hash.deref()
}
}
// helper to make Vec<u8> serialization use base64 representation to make it human readable
// so that it would be easier for users to copy contents from the disk if they wanted to use it elsewhere
mod base64 {
use base64::{engine::general_purpose::STANDARD, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&STANDARD.encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
STANDARD.decode(s).map_err(serde::de::Error::custom)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, thiserror::Error)]
pub enum OnDiskGatewayDetailsError {
#[error("JSON failure: {0}")]
SerializationFailure(#[from] serde_json::Error),
#[error("failed to store gateway details to {path}: {err}")]
StoreFailure {
path: String,
#[source]
err: std::io::Error,
},
#[error("failed to load gateway details from {path}: {err}")]
LoadFailure {
path: String,
#[source]
err: std::io::Error,
},
}
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskGatewayDetails {
file_location: std::path::PathBuf,
}
#[cfg(not(target_arch = "wasm32"))]
impl OnDiskGatewayDetails {
pub fn new<P: AsRef<std::path::Path>>(path: P) -> Self {
OnDiskGatewayDetails {
file_location: path.as_ref().to_owned(),
}
}
pub fn load_from_disk(&self) -> Result<PersistedGatewayDetails, OnDiskGatewayDetailsError> {
let file = std::fs::File::open(&self.file_location).map_err(|err| {
OnDiskGatewayDetailsError::LoadFailure {
path: self.file_location.display().to_string(),
err,
}
})?;
Ok(serde_json::from_reader(file)?)
}
pub fn store_to_disk(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), OnDiskGatewayDetailsError> {
// ensure the whole directory structure exists
if let Some(parent_dir) = &self.file_location.parent() {
std::fs::create_dir_all(parent_dir).map_err(|err| {
OnDiskGatewayDetailsError::StoreFailure {
path: self.file_location.display().to_string(),
err,
}
})?
}
let file = std::fs::File::create(&self.file_location).map_err(|err| {
OnDiskGatewayDetailsError::StoreFailure {
path: self.file_location.display().to_string(),
err,
}
})?;
Ok(serde_json::to_writer_pretty(file, details)?)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewayDetailsStore for OnDiskGatewayDetails {
type StorageError = OnDiskGatewayDetailsError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.load_from_disk()
}
async fn store_gateway_details(
&self,
gateway_details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
self.store_to_disk(gateway_details)
}
}
#[derive(Default)]
pub struct InMemGatewayDetails {
details: Mutex<Option<PersistedGatewayDetails>>,
}
#[derive(Debug, thiserror::Error)]
#[error("old ephemeral gateway details can't be loaded from storage")]
pub struct EphemeralGatewayDetailsError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewayDetailsStore for InMemGatewayDetails {
type StorageError = EphemeralGatewayDetailsError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.details
.lock()
.await
.clone()
.ok_or(EphemeralGatewayDetailsError)
}
async fn store_gateway_details(
&self,
gateway_details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
*self.details.lock().await = Some(gateway_details.clone());
Ok(())
}
}
@@ -4,46 +4,57 @@
// TODO: combine those more closely. Perhaps into a single underlying store. // TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases? // Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, InMemGatewayDetails,
};
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore}; use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage; use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend; use crate::client::replies::reply_storage::ReplyStorageBackend;
use nym_credential_storage::ephemeral_storage::{ use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
EphemeralStorage as EphemeralCredentialStorage, EphemeralStorage,
};
use nym_credential_storage::storage::Storage as CredentialStorage; use nym_credential_storage::storage::Storage as CredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::non_wasm_helpers; use crate::client::base_client::non_wasm_helpers;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::key_manager::persistence::OnDiskKeys; use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config}; use crate::client::replies::reply_storage::fs_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{self, disk_persistence::CommonClientPaths};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::error::ClientCoreError; use crate::error::ClientCoreError;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] pub mod gateway_details;
use crate::client::replies::reply_storage::fs_backend;
// TODO: ideally this should be changed into
// `MixnetClientStorage: KeyStore + ReplyStorageBackend + CredentialStorage + GatewayDetailsStore`
pub trait MixnetClientStorage { pub trait MixnetClientStorage {
type KeyStore: KeyStore; type KeyStore: KeyStore;
type ReplyStore: ReplyStorageBackend; type ReplyStore: ReplyStorageBackend;
type CredentialStore: CredentialStorage; type CredentialStore: CredentialStorage;
type GatewayDetailsStore: GatewayDetailsStore;
// this is a TERRIBLE name... // this is a TERRIBLE name...
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore); // fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore, Self::GatewayDetailsStore);
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore);
fn key_store(&self) -> &Self::KeyStore; fn key_store(&self) -> &Self::KeyStore;
fn reply_store(&self) -> &Self::ReplyStore; fn reply_store(&self) -> &Self::ReplyStore;
fn credential_store(&self) -> &Self::CredentialStore; fn credential_store(&self) -> &Self::CredentialStore;
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore;
} }
#[derive(Default)] #[derive(Default)]
pub struct Ephemeral { pub struct Ephemeral {
key_store: InMemEphemeralKeys, key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty, reply_store: reply_storage::Empty,
credential_store: EphemeralStorage, credential_store: EphemeralCredentialStorage,
gateway_details_store: InMemGatewayDetails,
} }
impl Ephemeral { impl Ephemeral {
@@ -56,9 +67,10 @@ impl MixnetClientStorage for Ephemeral {
type KeyStore = InMemEphemeralKeys; type KeyStore = InMemEphemeralKeys;
type ReplyStore = reply_storage::Empty; type ReplyStore = reply_storage::Empty;
type CredentialStore = EphemeralCredentialStorage; type CredentialStore = EphemeralCredentialStorage;
type GatewayDetailsStore = InMemGatewayDetails;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store) (self.reply_store, self.credential_store)
} }
fn key_store(&self) -> &Self::KeyStore { fn key_store(&self) -> &Self::KeyStore {
@@ -72,6 +84,10 @@ impl MixnetClientStorage for Ephemeral {
fn credential_store(&self) -> &Self::CredentialStore { fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store &self.credential_store
} }
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.gateway_details_store
}
} }
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -79,6 +95,7 @@ pub struct OnDiskPersistent {
pub(crate) key_store: OnDiskKeys, pub(crate) key_store: OnDiskKeys,
pub(crate) reply_store: fs_backend::Backend, pub(crate) reply_store: fs_backend::Backend,
pub(crate) credential_store: PersistentCredentialStorage, pub(crate) credential_store: PersistentCredentialStorage,
pub(crate) gateway_details_store: OnDiskGatewayDetails,
} }
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -87,31 +104,38 @@ impl OnDiskPersistent {
key_store: OnDiskKeys, key_store: OnDiskKeys,
reply_store: fs_backend::Backend, reply_store: fs_backend::Backend,
credential_store: PersistentCredentialStorage, credential_store: PersistentCredentialStorage,
gateway_details_store: OnDiskGatewayDetails,
) -> Self { ) -> Self {
Self { Self {
key_store, key_store,
reply_store, reply_store,
credential_store, credential_store,
gateway_details_store,
} }
} }
pub async fn from_config<T>(config: &Config<T>) -> Result<Self, ClientCoreError> { pub async fn from_paths(
let pathfinder = ClientKeyPathfinder::new_from_config(config); paths: CommonClientPaths,
let key_store = OnDiskKeys::new(pathfinder); debug_config: &config::DebugConfig,
) -> Result<Self, ClientCoreError> {
let key_store = OnDiskKeys::new(paths.keys);
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend( let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
config.get_reply_surb_database_path(), paths.reply_surb_database,
&config.get_debug_config().reply_surbs, &debug_config.reply_surbs,
) )
.await?; .await?;
let credential_store = let credential_store =
nym_credential_storage::initialise_persistent_storage(config.get_database_path()).await; nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await;
let gateway_details_store = OnDiskGatewayDetails::new(paths.gateway_details);
Ok(OnDiskPersistent { Ok(OnDiskPersistent {
key_store, key_store,
reply_store, reply_store,
credential_store, credential_store,
gateway_details_store,
}) })
} }
} }
@@ -121,9 +145,10 @@ impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys; type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend; type ReplyStore = fs_backend::Backend;
type CredentialStore = PersistentCredentialStorage; type CredentialStore = PersistentCredentialStorage;
type GatewayDetailsStore = OnDiskGatewayDetails;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store) (self.reply_store, self.credential_store)
} }
fn key_store(&self) -> &Self::KeyStore { fn key_store(&self) -> &Self::KeyStore {
@@ -137,4 +162,8 @@ impl MixnetClientStorage for OnDiskPersistent {
fn credential_store(&self) -> &Self::CredentialStore { fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store &self.credential_store
} }
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.gateway_details_store
}
} }
@@ -6,6 +6,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys; use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey; use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::fmt::{Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use zeroize::ZeroizeOnDrop; use zeroize::ZeroizeOnDrop;
@@ -20,6 +21,16 @@ pub enum ManagedKeys {
Invalidated, Invalidated,
} }
impl Debug for ManagedKeys {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ManagedKeys::Initial(_) => write!(f, "initial"),
ManagedKeys::FullyDerived(_) => write!(f, "fully derived"),
ManagedKeys::Invalidated => write!(f, "invalidated"),
}
}
}
impl From<KeyManagerBuilder> for ManagedKeys { impl From<KeyManagerBuilder> for ManagedKeys {
fn from(value: KeyManagerBuilder) -> Self { fn from(value: KeyManagerBuilder) -> Self {
ManagedKeys::Initial(value) ManagedKeys::Initial(value)
@@ -84,6 +95,11 @@ impl ManagedKeys {
} }
} }
pub fn must_get_gateway_shared_key(&self) -> Arc<SharedKeys> {
self.gateway_shared_key()
.expect("failed to extract gateway shared key")
}
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> { pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
match self { match self {
ManagedKeys::Initial(_) => None, ManagedKeys::Initial(_) => None,
@@ -108,6 +124,17 @@ impl ManagedKeys {
} }
} }
pub fn ensure_gateway_key(&self, gateway_shared_key: Arc<SharedKeys>) {
if let ManagedKeys::FullyDerived(key_manager) = &self {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|| key_manager.gateway_shared_key != gateway_shared_key
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
}
}
pub async fn deal_with_gateway_key<S: KeyStore>( pub async fn deal_with_gateway_key<S: KeyStore>(
&mut self, &mut self,
gateway_shared_key: Arc<SharedKeys>, gateway_shared_key: Arc<SharedKeys>,
@@ -120,12 +147,7 @@ impl ManagedKeys {
key_manager key_manager
} }
ManagedKeys::FullyDerived(key_manager) => { ManagedKeys::FullyDerived(key_manager) => {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key) self.ensure_gateway_key(gateway_shared_key);
|| key_manager.gateway_shared_key != gateway_shared_key
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
key_manager key_manager
} }
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"), ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
@@ -4,11 +4,10 @@
use crate::client::key_manager::KeyManager; use crate::client::key_manager::KeyManager;
use async_trait::async_trait; use async_trait::async_trait;
use std::error::Error; use std::error::Error;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder; use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::Config;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity}; use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@@ -38,6 +37,7 @@ pub enum OnDiskKeysError {
KeyPairLoadFailure { KeyPairLoadFailure {
keys: String, keys: String,
paths: nym_pemstore::KeyPairPath, paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error, err: std::io::Error,
}, },
@@ -45,6 +45,7 @@ pub enum OnDiskKeysError {
KeyPairStoreFailure { KeyPairStoreFailure {
keys: String, keys: String,
paths: nym_pemstore::KeyPairPath, paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error, err: std::io::Error,
}, },
@@ -52,6 +53,7 @@ pub enum OnDiskKeysError {
KeyLoadFailure { KeyLoadFailure {
key: String, key: String,
path: String, path: String,
#[source]
err: std::io::Error, err: std::io::Error,
}, },
@@ -59,30 +61,47 @@ pub enum OnDiskKeysError {
KeyStoreFailure { KeyStoreFailure {
key: String, key: String,
path: String, path: String,
#[source]
err: std::io::Error, err: std::io::Error,
}, },
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys { pub struct OnDiskKeys {
pathfinder: ClientKeyPathfinder, paths: ClientKeysPaths,
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
impl From<ClientKeyPathfinder> for OnDiskKeys { impl From<ClientKeysPaths> for OnDiskKeys {
fn from(pathfinder: ClientKeyPathfinder) -> Self { fn from(paths: ClientKeysPaths) -> Self {
OnDiskKeys { pathfinder } OnDiskKeys { paths }
} }
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
impl OnDiskKeys { impl OnDiskKeys {
pub fn new(pathfinder: ClientKeyPathfinder) -> Self { pub fn new(paths: ClientKeysPaths) -> Self {
OnDiskKeys { pathfinder } OnDiskKeys { paths }
} }
pub fn from_config<T>(config: &Config<T>) -> Self { #[doc(hidden)]
OnDiskKeys::new(ClientKeyPathfinder::new_from_config(config)) pub fn ephemeral_load_gateway_keys(
&self,
) -> Result<zeroize::Zeroizing<SharedKeys>, OnDiskKeysError> {
self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")
.map(zeroize::Zeroizing::new)
}
#[doc(hidden)]
pub fn load_encryption_keypair(&self) -> Result<encryption::KeyPair, OnDiskKeysError> {
let encryption_paths = self.paths.encryption_key_pair_path();
self.load_keypair(encryption_paths, "encryption keys")
}
#[doc(hidden)]
pub fn load_identity_keypair(&self) -> Result<identity::KeyPair, OnDiskKeysError> {
let identity_paths = self.paths.identity_key_pair_path();
self.load_keypair(identity_paths, "identity keys")
} }
fn load_key<T: PemStorableKey>( fn load_key<T: PemStorableKey>(
@@ -138,17 +157,12 @@ impl OnDiskKeys {
} }
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> { fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_paths = self.pathfinder.identity_key_pair_path(); let identity_keypair = self.load_identity_keypair()?;
let encryption_paths = self.pathfinder.encryption_key_pair_path(); let encryption_keypair = self.load_encryption_keypair()?;
let identity_keypair: identity::KeyPair = let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
self.load_keypair(identity_paths, "identity keys")?;
let encryption_keypair: encryption::KeyPair =
self.load_keypair(encryption_paths, "encryption keys")?;
let ack_key: AckKey = self.load_key(self.pathfinder.ack_key(), "ack key")?;
let gateway_shared_key: SharedKeys = let gateway_shared_key: SharedKeys =
self.load_key(self.pathfinder.gateway_shared_key(), "gateway shared keys")?; self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")?;
Ok(KeyManager::from_keys( Ok(KeyManager::from_keys(
identity_keypair, identity_keypair,
@@ -159,8 +173,8 @@ impl OnDiskKeys {
} }
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> { fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
let identity_paths = self.pathfinder.identity_key_pair_path(); let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.pathfinder.encryption_key_pair_path(); let encryption_paths = self.paths.encryption_key_pair_path();
self.store_keypair( self.store_keypair(
keys.identity_keypair.as_ref(), keys.identity_keypair.as_ref(),
@@ -173,10 +187,10 @@ impl OnDiskKeys {
"encryption keys", "encryption keys",
)?; )?;
self.store_key(keys.ack_key.as_ref(), self.pathfinder.ack_key(), "ack key")?; self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
self.store_key( self.store_key(
keys.gateway_shared_key.as_ref(), keys.gateway_shared_key.as_ref(),
self.pathfinder.gateway_shared_key(), self.paths.gateway_shared_key(),
"gateway shared keys", "gateway shared keys",
)?; )?;
@@ -199,10 +213,12 @@ impl KeyStore for OnDiskKeys {
} }
#[derive(Default)] #[derive(Default)]
pub struct InMemEphemeralKeys; pub struct InMemEphemeralKeys {
keys: Mutex<Option<KeyManager>>,
}
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("ephemeral keys can't be loaded from storage")] #[error("old ephemeral keys can't be loaded from storage")]
pub struct EphemeralKeysError; pub struct EphemeralKeysError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -211,10 +227,11 @@ impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError; type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> { async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
Err(EphemeralKeysError) self.keys.lock().await.clone().ok_or(EphemeralKeysError)
} }
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> { async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
*self.keys.lock().await = Some(keys.clone());
Ok(()) Ok(())
} }
} }
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use self::sending_delay_controller::SendingDelayController; use self::sending_delay_controller::SendingDelayController;
@@ -247,7 +247,7 @@ where
self.config.average_ack_delay, self.config.average_ack_delay,
self.config.traffic.average_packet_delay, self.config.traffic.average_packet_delay,
cover_traffic_packet_size, cover_traffic_packet_size,
self.config.traffic.packet_type.unwrap_or_default(), self.config.traffic.packet_type,
) )
.expect( .expect(
"Somehow failed to generate a loop cover message with a valid topology", "Somehow failed to generate a loop cover message with a valid topology",
@@ -0,0 +1,117 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_GATEWAY_SHARED_KEY_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientKeysPaths {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
}
impl ClientKeysPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
ClientKeysPaths {
private_identity_key_file: base_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME),
public_identity_key_file: base_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
private_encryption_key_file: base_dir.join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME),
public_encryption_key_file: base_dir.join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME),
gateway_shared_key_file: base_dir.join(DEFAULT_GATEWAY_SHARED_KEY_FILENAME),
ack_key_file: base_dir.join(DEFAULT_ACK_KEY_FILENAME),
}
}
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_identity_key().to_path_buf(),
self.public_identity_key().to_path_buf(),
)
}
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_encryption_key().to_path_buf(),
self.public_encryption_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
matches!(self.public_identity_key_file.try_exists(), Ok(true))
|| matches!(self.private_identity_key_file.try_exists(), Ok(true))
|| matches!(self.public_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.private_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
|| matches!(self.ack_key_file.try_exists(), Ok(true))
}
pub fn any_file_exists_and_return(&self) -> Option<PathBuf> {
file_exists(&self.public_identity_key_file)
.or_else(|| file_exists(&self.private_identity_key_file))
.or_else(|| file_exists(&self.public_encryption_key_file))
.or_else(|| file_exists(&self.private_encryption_key_file))
.or_else(|| file_exists(&self.gateway_shared_key_file))
.or_else(|| file_exists(&self.ack_key_file))
}
pub fn gateway_key_file_exists(&self) -> bool {
matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
}
pub fn private_identity_key(&self) -> &Path {
&self.private_identity_key_file
}
pub fn public_identity_key(&self) -> &Path {
&self.public_identity_key_file
}
pub fn private_encryption_key(&self) -> &Path {
&self.private_encryption_key_file
}
pub fn public_encryption_key(&self) -> &Path {
&self.public_encryption_key_file
}
pub fn gateway_shared_key(&self) -> &Path {
&self.gateway_shared_key_file
}
pub fn ack_key(&self) -> &Path {
&self.ack_key_file
}
}
fn file_exists(path: &Path) -> Option<PathBuf> {
if matches!(path.try_exists(), Ok(true)) {
return Some(path.to_path_buf());
}
None
}
@@ -0,0 +1,42 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub mod keys_paths;
pub mod old_v1_1_20_2;
pub const DEFAULT_GATEWAY_DETAILS_FILENAME: &str = "gateway_details.json";
pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db";
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPaths {
pub keys: ClientKeysPaths,
/// Path to the file containing information about gateway used by this client,
/// i.e. details such as its public key, owner address or the network information.
pub gateway_details: PathBuf,
/// Path to the database containing bandwidth credentials of this client.
pub credentials_database: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
pub reply_surb_database: PathBuf,
}
impl CommonClientPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
CommonClientPaths {
credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME),
reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME),
gateway_details: base_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
keys: ClientKeysPaths::new_default(base_data_directory),
}
}
}
@@ -0,0 +1,30 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV1_1_20_2 {
pub keys: ClientKeysPaths,
pub credentials_database: PathBuf,
pub reply_surb_database: PathBuf,
}
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 {
keys: self.keys,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
}
}
}
+48 -427
View File
@@ -1,13 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails; use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_sphinx::params::{PacketSize, PacketType}; use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use url::Url; use url::Url;
@@ -15,19 +12,10 @@ use crate::error::ClientCoreError;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
pub mod disk_persistence;
pub mod old_config_v1_1_13; pub mod old_config_v1_1_13;
pub mod persistence; pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_GATEWAY_KEYS_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
pub const DEFAULT_REPLY_STORE_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIAL_STORE_FILENAME: &str = CRED_DB_FILE_NAME;
pub const MISSING_VALUE: &str = "MISSING VALUE";
// 'DEBUG' // 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
@@ -68,172 +56,41 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6
// 24 hours // 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
pub trait ClientCoreConfigTrait {
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig;
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Config<T> { pub struct Config {
client: Client<T>, pub client: Client,
#[serde(default)] #[serde(default)]
logging: Logging, pub debug: DebugConfig,
#[serde(default)]
debug: DebugConfig,
} }
impl<T> ClientCoreConfigTrait for Config<T> { impl Config {
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig { pub fn new<S: Into<String>>(id: S, version: S) -> Self {
&self.client.gateway_endpoint Config {
client: Client::new_default(id, version),
debug: Default::default(),
}
} }
}
impl<T> OptionalSet for Config<T> where T: NymConfig {} pub fn from_client_config(client: Client, debug: DebugConfig) -> Self {
Config { client, debug }
impl<T> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self
where
T: NymConfig,
{
Config::default().with_id(id)
} }
pub fn validate(&self) -> bool { pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.debug.validate() self.debug.validate()
} }
#[must_use] pub fn with_debug_config(mut self, debug: DebugConfig) -> Self {
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self self.debug = debug;
where
T: NymConfig,
{
self.client.id = id.into();
self.set_empty_fields_to_defaults();
self self
} }
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_data_directory<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.client.private_identity_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME);
self.client.public_identity_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME);
self.client.private_encryption_key_file =
dir.as_ref().join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME);
self.client.public_encryption_key_file =
dir.as_ref().join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME);
self.client.gateway_shared_key_file = dir.as_ref().join(DEFAULT_GATEWAY_KEYS_FILENAME);
self.client.ack_key_file = dir.as_ref().join(DEFAULT_ACK_KEY_FILENAME);
self.client.reply_surb_database_path = dir.as_ref().join(DEFAULT_REPLY_STORE_FILENAME);
self.client.database_path = dir.as_ref().join(DEFAULT_CREDENTIAL_STORE_FILENAME);
self
}
#[must_use]
#[doc(hidden)]
// TODO: this totally contradicts our trait... we REALLY have to refactor it...
pub fn reset_nym_root_directory<P: AsRef<Path>>(mut self, dir: P) -> Self
where
T: NymConfig,
{
self.client.nym_root_directory = dir.as_ref().to_owned();
self
}
pub fn set_empty_fields_to_defaults(&mut self) -> bool
where
T: NymConfig,
{
let id = &self.client.id;
let mut changes_made = false;
// identity key setting
if self.client.private_identity_key_file.as_os_str().is_empty() {
changes_made = true;
self.client.private_identity_key_file =
self::Client::<T>::default_private_identity_key_file(id);
}
if self.client.public_identity_key_file.as_os_str().is_empty() {
changes_made = true;
self.client.public_identity_key_file =
self::Client::<T>::default_public_identity_key_file(id);
}
// encryption key setting
if self
.client
.private_encryption_key_file
.as_os_str()
.is_empty()
{
changes_made = true;
self.client.private_encryption_key_file =
self::Client::<T>::default_private_encryption_key_file(id);
}
if self
.client
.public_encryption_key_file
.as_os_str()
.is_empty()
{
changes_made = true;
self.client.public_encryption_key_file =
self::Client::<T>::default_public_encryption_key_file(id);
}
// shared gateway key setting
if self.client.gateway_shared_key_file.as_os_str().is_empty() {
changes_made = true;
self.client.gateway_shared_key_file =
self::Client::<T>::default_gateway_shared_key_file(id);
}
// ack key setting
if self.client.ack_key_file.as_os_str().is_empty() {
changes_made = true;
self.client.ack_key_file = self::Client::<T>::default_ack_key_file(id);
}
if self.client.reply_surb_database_path.as_os_str().is_empty() {
changes_made = true;
self.client.reply_surb_database_path =
self::Client::<T>::default_reply_surb_database_path(id);
}
if self.client.database_path.as_os_str().is_empty() {
changes_made = true;
self.client.database_path = self::Client::<T>::default_database_path(id);
}
changes_made
}
pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self { pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self {
self.client.disabled_credentials_mode = disabled_credentials_mode; self.client.disabled_credentials_mode = disabled_credentials_mode;
self self
} }
pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint;
}
pub fn with_gateway_endpoint(mut self, gateway_endpoint: GatewayEndpointConfig) -> Self {
self.client.gateway_endpoint = gateway_endpoint;
self
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_endpoint.gateway_id = id.into();
}
pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self { pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self {
self.client.nyxd_urls = urls; self.client.nyxd_urls = urls;
self self
@@ -260,7 +117,7 @@ impl<T> Config<T> {
} }
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self { pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.client.packet_type = Some(packet_type); self.debug.traffic.packet_type = packet_type;
self self
} }
@@ -280,6 +137,11 @@ impl<T> Config<T> {
self self
} }
pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self {
self.debug.topology.disable_refreshing = disable_topology_refresh;
self
}
pub fn set_no_cover_traffic(&mut self) { pub fn set_no_cover_traffic(&mut self) {
self.debug.cover_traffic.disable_loop_cover_traffic_stream = true; self.debug.cover_traffic.disable_loop_cover_traffic_stream = true;
self.debug.traffic.disable_main_poisson_packet_distribution = true; self.debug.traffic.disable_main_poisson_packet_distribution = true;
@@ -297,34 +159,6 @@ impl<T> Config<T> {
self.client.disabled_credentials_mode self.client.disabled_credentials_mode
} }
pub fn get_nym_root_directory(&self) -> PathBuf {
self.client.nym_root_directory.clone()
}
pub fn get_private_identity_key_file(&self) -> PathBuf {
self.client.private_identity_key_file.clone()
}
pub fn get_public_identity_key_file(&self) -> PathBuf {
self.client.public_identity_key_file.clone()
}
pub fn get_private_encryption_key_file(&self) -> PathBuf {
self.client.private_encryption_key_file.clone()
}
pub fn get_public_encryption_key_file(&self) -> PathBuf {
self.client.public_encryption_key_file.clone()
}
pub fn get_gateway_shared_key_file(&self) -> PathBuf {
self.client.gateway_shared_key_file.clone()
}
pub fn get_ack_key_file(&self) -> PathBuf {
self.client.ack_key_file.clone()
}
pub fn get_validator_endpoints(&self) -> Vec<Url> { pub fn get_validator_endpoints(&self) -> Vec<Url> {
self.client.nyxd_urls.clone() self.client.nyxd_urls.clone()
} }
@@ -332,139 +166,6 @@ impl<T> Config<T> {
pub fn get_nym_api_endpoints(&self) -> Vec<Url> { pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.client.nym_api_urls.clone() self.client.nym_api_urls.clone()
} }
pub fn get_gateway_id(&self) -> String {
self.client.gateway_endpoint.gateway_id.clone()
}
pub fn get_gateway_owner(&self) -> String {
self.client.gateway_endpoint.gateway_owner.clone()
}
pub fn get_gateway_listener(&self) -> String {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
pub fn get_database_path(&self) -> PathBuf {
self.client.database_path.clone()
}
pub fn get_reply_surb_database_path(&self) -> PathBuf {
self.client.reply_surb_database_path.clone()
}
pub fn get_version(&self) -> &str {
&self.client.version
}
// Debug getters
pub fn get_debug_config(&self) -> &DebugConfig {
&self.debug
}
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.traffic.average_packet_delay
}
pub fn get_average_ack_delay(&self) -> Duration {
self.debug.acknowledgements.average_ack_delay
}
pub fn get_ack_wait_multiplier(&self) -> f64 {
self.debug.acknowledgements.ack_wait_multiplier
}
pub fn get_ack_wait_addition(&self) -> Duration {
self.debug.acknowledgements.ack_wait_addition
}
pub fn get_loop_cover_traffic_average_delay(&self) -> Duration {
self.debug.cover_traffic.loop_cover_traffic_average_delay
}
pub fn get_message_sending_average_delay(&self) -> Duration {
self.debug.traffic.message_sending_average_delay
}
pub fn get_gateway_response_timeout(&self) -> Duration {
self.debug.gateway_connection.gateway_response_timeout
}
pub fn get_topology_refresh_rate(&self) -> Duration {
self.debug.topology.topology_refresh_rate
}
pub fn get_topology_resolution_timeout(&self) -> Duration {
self.debug.topology.topology_resolution_timeout
}
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
self.debug.cover_traffic.disable_loop_cover_traffic_stream
}
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
self.debug.traffic.disable_main_poisson_packet_distribution
}
pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize {
self.debug.reply_surbs.minimum_reply_surb_storage_threshold
}
pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize {
self.debug.reply_surbs.maximum_reply_surb_storage_threshold
}
pub fn get_minimum_reply_surb_request_size(&self) -> u32 {
self.debug.reply_surbs.minimum_reply_surb_request_size
}
pub fn get_maximum_reply_surb_request_size(&self) -> u32 {
self.debug.reply_surbs.maximum_reply_surb_request_size
}
pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 {
self.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size
}
pub fn get_maximum_reply_surb_rerequest_waiting_period(&self) -> Duration {
self.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period
}
pub fn get_maximum_reply_surb_drop_waiting_period(&self) -> Duration {
self.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period
}
pub fn get_maximum_reply_surb_age(&self) -> Duration {
self.debug.reply_surbs.maximum_reply_surb_age
}
pub fn get_maximum_reply_key_age(&self) -> Duration {
self.debug.reply_surbs.maximum_reply_key_age
}
pub fn get_packet_type(&self) -> PacketType {
self.client.packet_type.unwrap_or(PacketType::Mix)
}
}
impl<T: NymConfig> Default for Config<T> {
fn default() -> Self {
Config {
client: Client::<T>::default(),
logging: Default::default(),
debug: Default::default(),
}
}
} }
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
@@ -517,9 +218,10 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
} }
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct Client<T> { // note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct Client {
/// Version of the client for which this configuration was created. /// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
pub version: String, pub version: String,
/// ID specifies the human readable ID of this particular client. /// ID specifies the human readable ID of this particular client.
@@ -527,6 +229,7 @@ pub struct Client<T> {
/// Indicates whether this client is running in a disabled credentials mode, thus attempting /// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials. /// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)] #[serde(default)]
pub disabled_credentials_mode: bool, pub disabled_credentials_mode: bool,
@@ -537,52 +240,10 @@ pub struct Client<T> {
/// Addresses to APIs running on validator from which the client gets the view of the network. /// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")] #[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>, pub nym_api_urls: Vec<Url>,
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
/// Information regarding how the client should send data to gateway.
pub gateway_endpoint: GatewayEndpointConfig,
/// Path to the database containing bandwidth credentials of this client.
pub database_path: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
// this was set to use #[serde(default)] for the purposes of compatibility for multi-surbs introduced in 1.1.4.
// if you're reading this message and we have already introduced some breaking changes, feel free
// to remove that attribute since at this point the client configs should have gotten regenerated
#[serde(default)]
pub reply_surb_database_path: PathBuf,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
pub nym_root_directory: PathBuf,
#[serde(skip)]
pub super_struct: PhantomData<T>,
pub packet_type: Option<PacketType>,
} }
impl<T: NymConfig> Default for Client<T> { impl Client {
fn default() -> Self { pub fn new_default<S: Into<String>>(id: S, version: S) -> Self {
let network = NymNetworkDetails::new_mainnet(); let network = NymNetworkDetails::new_mainnet();
let nyxd_urls = network let nyxd_urls = network
.endpoints .endpoints
@@ -595,71 +256,31 @@ impl<T: NymConfig> Default for Client<T> {
.filter_map(|validator| validator.api_url()) .filter_map(|validator| validator.api_url())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if nym_api_urls.is_empty() {
panic!("we do not have any default nym-api urls available!")
}
// there must be explicit checks for whether id is not empty later
Client { Client {
version: env!("CARGO_PKG_VERSION").to_string(), version: version.into(),
id: "".to_string(), id: id.into(),
disabled_credentials_mode: true, disabled_credentials_mode: true,
nyxd_urls, nyxd_urls,
nym_api_urls, nym_api_urls,
private_identity_key_file: Default::default(), }
public_identity_key_file: Default::default(), }
private_encryption_key_file: Default::default(),
public_encryption_key_file: Default::default(), pub fn new<S: Into<String>>(
gateway_shared_key_file: Default::default(), id: S,
ack_key_file: Default::default(), disabled_credentials_mode: bool,
gateway_endpoint: Default::default(), nyxd_urls: Vec<Url>,
database_path: Default::default(), nym_api_urls: Vec<Url>,
reply_surb_database_path: Default::default(), ) -> Self {
nym_root_directory: T::default_root_directory(), Client {
super_struct: Default::default(), version: env!("CARGO_PKG_VERSION").to_string(),
packet_type: Default::default(), id: id.into(),
disabled_credentials_mode,
nyxd_urls,
nym_api_urls,
} }
} }
} }
impl<T: NymConfig> Client<T> {
fn default_private_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("public_identity.pem")
}
fn default_private_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("private_encryption.pem")
}
fn default_public_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("public_encryption.pem")
}
fn default_gateway_shared_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("gateway_shared.pem")
}
fn default_ack_key_file(id: &str) -> PathBuf {
T::default_data_directory(id).join("ack_key.pem")
}
fn default_reply_surb_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join("persistent_reply_store.sqlite")
}
fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join(CRED_DB_FILE_NAME)
}
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)] #[serde(default)]
pub struct Traffic { pub struct Traffic {
@@ -690,7 +311,7 @@ pub struct Traffic {
/// Do not set it it unless you understand the consequences of that change. /// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>, pub secondary_packet_size: Option<PacketSize>,
pub packet_type: Option<PacketType>, pub packet_type: PacketType,
} }
impl Traffic { impl Traffic {
@@ -714,7 +335,7 @@ impl Default for Traffic {
disable_main_poisson_packet_distribution: false, disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket, primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None, secondary_packet_size: None,
packet_type: None, packet_type: PacketType::Mix,
} }
} }
} }
@@ -1,19 +1,18 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::config::{ use crate::config::old_config_v1_1_20::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, Logging, AcknowledgementsV1_1_20, ClientV1_1_20, ConfigV1_1_20, CoverTrafficV1_1_20, DebugConfigV1_1_20,
ReplySurbs, Topology, Traffic, DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, GatewayConnectionV1_1_20, LoggingV1_1_20, ReplySurbsV1_1_20, TopologyV1_1_20, TrafficV1_1_20,
DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT, DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, DEFAULT_GATEWAY_RESPONSE_TIMEOUT, DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_KEY_AGE,
DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_SURB_AGE, DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
}; };
use nym_config::NymConfig;
use nym_sphinx::params::PacketSize; use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -40,21 +39,21 @@ impl From<ExtendedPacketSize> for PacketSize {
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13<T> { pub struct OldConfigV1_1_13<T> {
pub client: Client<T>, pub client: ClientV1_1_20<T>,
#[serde(default)] #[serde(default)]
logging: Logging, pub logging: OldLoggingV1_1_13,
#[serde(default)] #[serde(default)]
debug: OldDebugConfigV1_1_13, pub debug: OldDebugConfigV1_1_13,
} }
impl<T: NymConfig> Default for OldConfigV1_1_13<T> { #[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
fn default() -> Self { #[serde(deny_unknown_fields)]
OldConfigV1_1_13 { pub struct OldLoggingV1_1_13 {}
client: Client::<T>::default(),
logging: Default::default(), impl From<OldLoggingV1_1_13> for LoggingV1_1_20 {
debug: Default::default(), fn from(_value: OldLoggingV1_1_13) -> Self {
} LoggingV1_1_20 {}
} }
} }
@@ -115,37 +114,36 @@ pub struct OldDebugConfigV1_1_13 {
pub maximum_reply_key_age: Duration, pub maximum_reply_key_age: Duration,
} }
impl From<OldDebugConfigV1_1_13> for DebugConfig { impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_20 {
fn from(value: OldDebugConfigV1_1_13) -> Self { fn from(value: OldDebugConfigV1_1_13) -> Self {
DebugConfig { DebugConfigV1_1_20 {
traffic: Traffic { traffic: TrafficV1_1_20 {
average_packet_delay: value.average_packet_delay, average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay, message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution, .disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket, primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: value.use_extended_packet_size.map(Into::into), secondary_packet_size: value.use_extended_packet_size.map(Into::into),
packet_type: None,
}, },
cover_traffic: CoverTraffic { cover_traffic: CoverTrafficV1_1_20 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
..CoverTraffic::default() ..CoverTrafficV1_1_20::default()
}, },
gateway_connection: GatewayConnection { gateway_connection: GatewayConnectionV1_1_20 {
gateway_response_timeout: value.gateway_response_timeout, gateway_response_timeout: value.gateway_response_timeout,
}, },
acknowledgements: Acknowledgements { acknowledgements: AcknowledgementsV1_1_20 {
average_ack_delay: value.average_ack_delay, average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition, ack_wait_addition: value.ack_wait_addition,
}, },
topology: Topology { topology: TopologyV1_1_20 {
topology_refresh_rate: value.topology_refresh_rate, topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout, topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false, disable_refreshing: false,
}, },
reply_surbs: ReplySurbs { reply_surbs: ReplySurbsV1_1_20 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
@@ -192,10 +190,10 @@ impl Default for OldDebugConfigV1_1_13 {
} }
} }
impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> { impl<T, U> From<OldConfigV1_1_13<T>> for ConfigV1_1_20<U> {
fn from(value: OldConfigV1_1_13<T>) -> Self { fn from(value: OldConfigV1_1_13<T>) -> Self {
Config { ConfigV1_1_20 {
client: Client { client: ClientV1_1_20 {
version: value.client.version, version: value.client.version,
id: value.client.id, id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode, disabled_credentials_mode: value.client.disabled_credentials_mode,
@@ -211,10 +209,10 @@ impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
database_path: value.client.database_path, database_path: value.client.database_path,
reply_surb_database_path: value.client.reply_surb_database_path, reply_surb_database_path: value.client.reply_surb_database_path,
nym_root_directory: value.client.nym_root_directory, nym_root_directory: value.client.nym_root_directory,
super_struct: PhantomData, super_struct: PhantomData,
packet_type: Some(nym_sphinx::params::PacketType::Mix),
}, },
logging: value.logging, logging: value.logging.into(),
debug: value.debug.into(), debug: value.debug.into(),
} }
} }
@@ -0,0 +1,339 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20_2::{
AcknowledgementsV1_1_20_2, CoverTrafficV1_1_20_2, DebugConfigV1_1_20_2,
GatewayConnectionV1_1_20_2, GatewayEndpointConfigV1_1_20_2, ReplySurbsV1_1_20_2,
TopologyV1_1_20_2, TrafficV1_1_20_2,
};
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
// 'DEBUG'
pub(crate) const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
pub(crate) const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
pub(crate) const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
pub(crate) const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
pub(crate) const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
pub(crate) const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
pub(crate) const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
pub(crate) const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
pub(crate) const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
pub(crate) const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
pub(crate) const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
pub(crate) const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration =
Duration::from_secs(10);
pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration =
Duration::from_secs(5 * 60);
// 12 hours
pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
pub(crate) const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20<T> {
pub client: ClientV1_1_20<T>,
#[serde(default)]
pub logging: LoggingV1_1_20,
#[serde(default)]
pub debug: DebugConfigV1_1_20,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20 {
pub gateway_id: String,
pub gateway_owner: String,
pub gateway_listener: String,
}
impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
fn from(value: GatewayEndpointConfigV1_1_20) -> Self {
GatewayEndpointConfigV1_1_20_2 {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_20<T> {
pub version: String,
pub id: String,
#[serde(default)]
pub disabled_credentials_mode: bool,
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
pub private_identity_key_file: PathBuf,
pub public_identity_key_file: PathBuf,
pub private_encryption_key_file: PathBuf,
pub public_encryption_key_file: PathBuf,
pub gateway_shared_key_file: PathBuf,
pub ack_key_file: PathBuf,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20,
pub database_path: PathBuf,
#[serde(default)]
pub reply_surb_database_path: PathBuf,
pub nym_root_directory: PathBuf,
#[serde(skip)]
pub super_struct: PhantomData<T>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingV1_1_20 {}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_20 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
pub disable_main_poisson_packet_distribution: bool,
pub primary_packet_size: PacketSize,
pub secondary_packet_size: Option<PacketSize>,
}
impl From<TrafficV1_1_20> for TrafficV1_1_20_2 {
fn from(value: TrafficV1_1_20) -> Self {
TrafficV1_1_20_2 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution,
primary_packet_size: value.primary_packet_size,
secondary_packet_size: value.secondary_packet_size,
packet_type: PacketType::Mix,
}
}
}
impl Default for TrafficV1_1_20 {
fn default() -> Self {
TrafficV1_1_20 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_20 {
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
pub cover_traffic_primary_size_ratio: f64,
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficV1_1_20> for CoverTrafficV1_1_20_2 {
fn from(value: CoverTrafficV1_1_20) -> Self {
CoverTrafficV1_1_20_2 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
}
}
}
impl Default for CoverTrafficV1_1_20 {
fn default() -> Self {
CoverTrafficV1_1_20 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_20 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20> for GatewayConnectionV1_1_20_2 {
fn from(value: GatewayConnectionV1_1_20) -> Self {
GatewayConnectionV1_1_20_2 {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_20 {
fn default() -> Self {
GatewayConnectionV1_1_20 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_20 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20> for AcknowledgementsV1_1_20_2 {
fn from(value: AcknowledgementsV1_1_20) -> Self {
AcknowledgementsV1_1_20_2 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
}
}
}
impl Default for AcknowledgementsV1_1_20 {
fn default() -> Self {
AcknowledgementsV1_1_20 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_20 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
pub disable_refreshing: bool,
}
impl From<TopologyV1_1_20> for TopologyV1_1_20_2 {
fn from(value: TopologyV1_1_20) -> Self {
TopologyV1_1_20_2 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
}
}
}
impl Default for TopologyV1_1_20 {
fn default() -> Self {
TopologyV1_1_20 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_20 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
pub maximum_reply_surb_request_size: u32,
pub maximum_allowed_reply_surb_request_size: u32,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
}
impl From<ReplySurbsV1_1_20> for ReplySurbsV1_1_20_2 {
fn from(value: ReplySurbsV1_1_20) -> Self {
ReplySurbsV1_1_20_2 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.maximum_reply_surb_age,
maximum_reply_key_age: value.maximum_reply_key_age,
}
}
}
impl Default for ReplySurbsV1_1_20 {
fn default() -> Self {
ReplySurbsV1_1_20 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_20 {
pub traffic: TrafficV1_1_20,
pub cover_traffic: CoverTrafficV1_1_20,
pub gateway_connection: GatewayConnectionV1_1_20,
pub acknowledgements: AcknowledgementsV1_1_20,
pub topology: TopologyV1_1_20,
pub reply_surbs: ReplySurbsV1_1_20,
}
impl From<DebugConfigV1_1_20> for DebugConfigV1_1_20_2 {
fn from(value: DebugConfigV1_1_20) -> Self {
DebugConfigV1_1_20_2 {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
acknowledgements: value.acknowledgements.into(),
topology: value.topology.into(),
reply_surbs: value.reply_surbs.into(),
}
}
}
@@ -0,0 +1,348 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection,
GatewayEndpointConfig, ReplySurbs, Topology, Traffic,
};
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
pub client: ClientV1_1_20_2,
#[serde(default)]
pub debug: DebugConfigV1_1_20_2,
}
impl From<ConfigV1_1_20_2> for Config {
fn from(value: ConfigV1_1_20_2) -> Self {
Config {
client: value.client.into(),
debug: value.debug.into(),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20_2 {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
/// Address of the gateway owner to which the client should send messages.
pub gateway_owner: String,
/// Address of the gateway listener to which all client requests should be sent.
pub gateway_listener: String,
}
impl From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self {
GatewayEndpointConfig {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_20_2 {
pub version: String,
pub id: String,
#[serde(default)]
pub disabled_credentials_mode: bool,
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20_2,
}
impl From<ClientV1_1_20_2> for Client {
fn from(value: ClientV1_1_20_2) -> Self {
Client {
version: value.version,
id: value.id,
disabled_credentials_mode: value.disabled_credentials_mode,
nyxd_urls: value.nyxd_urls,
nym_api_urls: value.nym_api_urls,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
pub disable_main_poisson_packet_distribution: bool,
pub primary_packet_size: PacketSize,
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl From<TrafficV1_1_20_2> for Traffic {
fn from(value: TrafficV1_1_20_2) -> Self {
Traffic {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution,
primary_packet_size: value.primary_packet_size,
secondary_packet_size: value.secondary_packet_size,
packet_type: PacketType::Mix,
}
}
}
impl Default for TrafficV1_1_20_2 {
fn default() -> Self {
TrafficV1_1_20_2 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
pub cover_traffic_primary_size_ratio: f64,
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficV1_1_20_2> for CoverTraffic {
fn from(value: CoverTrafficV1_1_20_2) -> Self {
CoverTraffic {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
}
}
}
impl Default for CoverTrafficV1_1_20_2 {
fn default() -> Self {
CoverTrafficV1_1_20_2 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20_2> for GatewayConnection {
fn from(value: GatewayConnectionV1_1_20_2) -> Self {
GatewayConnection {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_20_2 {
fn default() -> Self {
GatewayConnectionV1_1_20_2 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20_2> for Acknowledgements {
fn from(value: AcknowledgementsV1_1_20_2) -> Self {
Acknowledgements {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
}
}
}
impl Default for AcknowledgementsV1_1_20_2 {
fn default() -> Self {
AcknowledgementsV1_1_20_2 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
pub disable_refreshing: bool,
}
impl Default for TopologyV1_1_20_2 {
fn default() -> Self {
TopologyV1_1_20_2 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
}
}
}
impl From<TopologyV1_1_20_2> for Topology {
fn from(value: TopologyV1_1_20_2) -> Self {
Topology {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_20_2 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
pub maximum_reply_surb_request_size: u32,
pub maximum_allowed_reply_surb_request_size: u32,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
}
impl Default for ReplySurbsV1_1_20_2 {
fn default() -> Self {
ReplySurbsV1_1_20_2 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
}
}
}
impl From<ReplySurbsV1_1_20_2> for ReplySurbs {
fn from(value: ReplySurbsV1_1_20_2) -> Self {
ReplySurbs {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.maximum_reply_surb_age,
maximum_reply_key_age: value.maximum_reply_key_age,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_20_2 {
pub traffic: TrafficV1_1_20_2,
pub cover_traffic: CoverTrafficV1_1_20_2,
pub gateway_connection: GatewayConnectionV1_1_20_2,
pub acknowledgements: AcknowledgementsV1_1_20_2,
pub topology: TopologyV1_1_20_2,
pub reply_surbs: ReplySurbsV1_1_20_2,
}
impl From<DebugConfigV1_1_20_2> for DebugConfig {
fn from(value: DebugConfigV1_1_20_2) -> Self {
DebugConfig {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
acknowledgements: value.acknowledgements.into(),
topology: value.topology.into(),
reply_surbs: value.reply_surbs.into(),
}
}
}
@@ -1,108 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct ClientKeyPathfinder {
pub identity_private_key: PathBuf,
pub identity_public_key: PathBuf,
pub encryption_private_key: PathBuf,
pub encryption_public_key: PathBuf,
pub gateway_shared_key: PathBuf,
pub ack_key: PathBuf,
}
impl ClientKeyPathfinder {
pub fn new(id: String) -> Self {
let os_config_dir = dirs::config_dir().expect("no config directory known for this OS"); // grabs the OS default config dir
let config_dir = os_config_dir.join("nym").join("clients").join(id);
ClientKeyPathfinder {
identity_private_key: config_dir.join("private_identity.pem"),
identity_public_key: config_dir.join("public_identity.pem"),
encryption_private_key: config_dir.join("private_encryption.pem"),
encryption_public_key: config_dir.join("public_encryption.pem"),
gateway_shared_key: config_dir.join("gateway_shared.pem"),
ack_key: config_dir.join("ack_key.pem"),
}
}
pub fn new_from_config<T>(config: &Config<T>) -> Self {
ClientKeyPathfinder {
identity_private_key: config.get_private_identity_key_file(),
identity_public_key: config.get_public_identity_key_file(),
encryption_private_key: config.get_private_encryption_key_file(),
encryption_public_key: config.get_public_encryption_key_file(),
gateway_shared_key: config.get_gateway_shared_key_file(),
ack_key: config.get_ack_key_file(),
}
}
pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_identity_key().to_path_buf(),
self.public_identity_key().to_path_buf(),
)
}
pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(
self.private_encryption_key().to_path_buf(),
self.public_encryption_key().to_path_buf(),
)
}
pub fn any_file_exists(&self) -> bool {
matches!(self.identity_public_key.try_exists(), Ok(true))
|| matches!(self.identity_private_key.try_exists(), Ok(true))
|| matches!(self.encryption_public_key.try_exists(), Ok(true))
|| matches!(self.encryption_private_key.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key.try_exists(), Ok(true))
|| matches!(self.ack_key.try_exists(), Ok(true))
}
pub fn any_file_exists_and_return(&self) -> Option<PathBuf> {
file_exists(&self.identity_public_key)
.or_else(|| file_exists(&self.identity_private_key))
.or_else(|| file_exists(&self.encryption_public_key))
.or_else(|| file_exists(&self.encryption_private_key))
.or_else(|| file_exists(&self.gateway_shared_key))
.or_else(|| file_exists(&self.ack_key))
}
pub fn gateway_key_file_exists(&self) -> bool {
matches!(self.gateway_shared_key.try_exists(), Ok(true))
}
pub fn private_identity_key(&self) -> &Path {
&self.identity_private_key
}
pub fn public_identity_key(&self) -> &Path {
&self.identity_public_key
}
pub fn private_encryption_key(&self) -> &Path {
&self.encryption_private_key
}
pub fn public_encryption_key(&self) -> &Path {
&self.encryption_public_key
}
pub fn gateway_shared_key(&self) -> &Path {
&self.gateway_shared_key
}
pub fn ack_key(&self) -> &Path {
&self.ack_key
}
}
fn file_exists(path: &Path) -> Option<PathBuf> {
if matches!(path.try_exists(), Ok(true)) {
return Some(path.to_path_buf());
}
None
}
@@ -1,4 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod key_pathfinder;
+19
View File
@@ -50,6 +50,11 @@ pub enum ClientCoreError {
source: Box<dyn Error + Send + Sync>, source: Box<dyn Error + Send + Sync>,
}, },
#[error("experienced a failure with our gateway details storage: {source}")]
GatewayDetailsStoreError {
source: Box<dyn Error + Send + Sync>,
},
#[error("The gateway id is invalid - {0}")] #[error("The gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError), UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
@@ -97,6 +102,20 @@ pub enum ClientCoreError {
"This operation would have resulted in clients keys being overwritten without permission" "This operation would have resulted in clients keys being overwritten without permission"
)] )]
ForbiddenKeyOverwrite, ForbiddenKeyOverwrite,
#[error("gateway details are unavailable")]
UnavailableGatewayDetails {
source: Box<dyn Error + Send + Sync>,
},
#[error("gateway shared key is unavailable whilst we have full node information")]
UnavailableSharedKey,
#[error("attempted to obtain fresh gateway details whilst already knowing about one")]
UnexpectedGatewayDetails,
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
MismatchedGatewayDetails { gateway_id: String },
} }
/// Set of messages that the client can send to listeners via the task manager /// Set of messages that the client can send to listeners via the task manager
+9 -9
View File
@@ -44,18 +44,18 @@ const MEASUREMENTS: usize = 3;
const CONN_TIMEOUT: Duration = Duration::from_millis(1500); const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000); const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency { struct GatewayWithLatency<'a> {
gateway: gateway::Node, gateway: &'a gateway::Node,
latency: Duration, latency: Duration,
} }
impl GatewayWithLatency { impl<'a> GatewayWithLatency<'a> {
fn new(gateway: gateway::Node, latency: Duration) -> Self { fn new(gateway: &'a gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency } GatewayWithLatency { gateway, latency }
} }
} }
pub(super) async fn current_gateways<R: Rng>( pub async fn current_gateways<R: Rng>(
rng: &mut R, rng: &mut R,
nym_apis: &[Url], nym_apis: &[Url],
) -> Result<Vec<gateway::Node>, ClientCoreError> { ) -> Result<Vec<gateway::Node>, ClientCoreError> {
@@ -64,7 +64,7 @@ pub(super) async fn current_gateways<R: Rng>(
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone()); let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api); log::trace!("Fetching list of gateways from: {nym_api}");
let gateways = client.get_cached_gateways().await?; let gateways = client.get_cached_gateways().await?;
let valid_gateways = gateways let valid_gateways = gateways
@@ -91,7 +91,7 @@ async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure) JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
} }
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> { async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address(); let addr = gateway.clients_address();
trace!( trace!(
"establishing connection to {} ({addr})...", "establishing connection to {} ({addr})...",
@@ -156,7 +156,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, C
pub(super) async fn choose_gateway_by_latency<R: Rng>( pub(super) async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R, rng: &mut R,
gateways: Vec<gateway::Node>, gateways: &[gateway::Node],
) -> Result<gateway::Node, ClientCoreError> { ) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency..."); info!("choosing gateway by latency...");
@@ -189,7 +189,7 @@ pub(super) async fn choose_gateway_by_latency<R: Rng>(
pub(super) fn uniformly_random_gateway<R: Rng>( pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R, rng: &mut R,
gateways: Vec<gateway::Node>, gateways: &[gateway::Node],
) -> Result<gateway::Node, ClientCoreError> { ) -> Result<gateway::Node, ClientCoreError> {
gateways gateways
.choose(rng) .choose(rng)
+274 -205
View File
@@ -3,31 +3,82 @@
//! Collection of initialization steps used by client implementations //! Collection of initialization steps used by client implementations
use crate::client::base_client::storage::MixnetClientStorage; use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedGatewayDetails,
};
use crate::client::key_manager::persistence::KeyStore; use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::{KeyManager, ManagedKeys}; use crate::client::key_manager::ManagedKeys;
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway}; use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
use crate::{ use crate::{
config::{ config::{Config, GatewayEndpointConfig},
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
GatewayEndpointConfig,
},
error::ClientCoreError, error::ClientCoreError,
}; };
use nym_config::NymConfig; use nym_crypto::asymmetric::identity;
use nym_crypto::asymmetric::{encryption, identity};
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use nym_topology::gateway;
use nym_validator_client::client::IdentityKey; use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use serde::Serialize; use serde::Serialize;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use tap::TapFallible;
use url::Url; use url::Url;
mod helpers; pub mod helpers;
#[derive(Clone)] // TODO: rename to something better...
#[derive(Debug)]
pub struct InitialisationDetails {
pub gateway_details: GatewayEndpointConfig,
pub managed_keys: ManagedKeys,
}
impl InitialisationDetails {
pub fn new(gateway_details: GatewayEndpointConfig, managed_keys: ManagedKeys) -> Self {
InitialisationDetails {
gateway_details,
managed_keys,
}
}
pub async fn try_load<K, D>(key_store: &K, details_store: &D) -> Result<Self, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
let loaded_details = _load_gateway_details(details_store).await?;
let loaded_keys = _load_managed_keys(key_store).await?;
if !loaded_details.verify(&loaded_keys.must_get_gateway_shared_key()) {
return Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: loaded_details.details.gateway_id,
});
}
Ok(InitialisationDetails {
gateway_details: loaded_details.into(),
managed_keys: loaded_keys,
})
}
pub fn client_address(&self) -> Result<Recipient, ClientCoreError> {
let client_recipient = Recipient::new(
*self.managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_details.gateway_id)?,
);
Ok(client_recipient)
}
}
#[derive(Debug, Clone)]
pub enum GatewaySetup { pub enum GatewaySetup {
/// The gateway specification MUST BE loaded from the underlying storage.
MustLoad,
/// Specifies usage of a new, random, gateway. /// Specifies usage of a new, random, gateway.
New { New {
/// Should the new gateway be selected based on latency. /// Should the new gateway be selected based on latency.
@@ -39,13 +90,13 @@ pub enum GatewaySetup {
}, },
Predefined { Predefined {
/// Full gateway configuration /// Full gateway configuration
config: GatewayEndpointConfig, details: PersistedGatewayDetails,
}, },
} }
impl From<GatewayEndpointConfig> for GatewaySetup { impl From<PersistedGatewayDetails> for GatewaySetup {
fn from(config: GatewayEndpointConfig) -> Self { fn from(details: PersistedGatewayDetails) -> Self {
GatewaySetup::Predefined { config } GatewaySetup::Predefined { details }
} }
} }
@@ -62,14 +113,11 @@ impl Default for GatewaySetup {
} }
impl GatewaySetup { impl GatewaySetup {
pub fn new( pub fn new_fresh(
full_config: Option<GatewayEndpointConfig>, gateway_identity: Option<String>,
gateway_identity: Option<IdentityKey>,
latency_based_selection: Option<bool>, latency_based_selection: Option<bool>,
) -> Self { ) -> Self {
if let Some(config) = full_config { if let Some(gateway_identity) = gateway_identity {
GatewaySetup::Predefined { config }
} else if let Some(gateway_identity) = gateway_identity {
GatewaySetup::Specified { gateway_identity } GatewaySetup::Specified { gateway_identity }
} else { } else {
GatewaySetup::New { GatewaySetup::New {
@@ -78,15 +126,22 @@ impl GatewaySetup {
} }
} }
pub async fn try_get_gateway_details( pub fn is_must_load(&self) -> bool {
self, matches!(self, GatewaySetup::MustLoad)
validator_servers: &[Url], }
pub fn has_full_details(&self) -> bool {
matches!(self, GatewaySetup::Predefined { .. }) || self.is_must_load()
}
pub async fn choose_gateway(
&self,
gateways: &[gateway::Node],
) -> Result<GatewayEndpointConfig, ClientCoreError> { ) -> Result<GatewayEndpointConfig, ClientCoreError> {
match self { match self {
GatewaySetup::New { by_latency } => { GatewaySetup::New { by_latency } => {
let mut rng = OsRng; let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?; if *by_latency {
if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await choose_gateway_by_latency(&mut rng, gateways).await
} else { } else {
uniformly_random_gateway(&mut rng, gateways) uniformly_random_gateway(&mut rng, gateways)
@@ -94,20 +149,28 @@ impl GatewaySetup {
} }
.map(Into::into), .map(Into::into),
GatewaySetup::Specified { gateway_identity } => { GatewaySetup::Specified { gateway_identity } => {
let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity) let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
gateways gateways
.into_iter() .iter()
.find(|gateway| gateway.identity_key == user_gateway) .find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string())) .ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
.cloned()
} }
.map(Into::into), .map(Into::into),
GatewaySetup::Predefined { config } => Ok(config), _ => Err(ClientCoreError::UnexpectedGatewayDetails),
} }
} }
pub async fn try_get_new_gateway_details(
&self,
validator_servers: &[Url],
) -> Result<GatewayEndpointConfig, ClientCoreError> {
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
self.choose_gateway(&gateways).await
}
} }
/// Struct describing the results of the client initialization procedure. /// Struct describing the results of the client initialization procedure.
@@ -122,17 +185,14 @@ pub struct InitResults {
} }
impl InitResults { impl InitResults {
pub fn new<T>(config: &Config<T>, address: &Recipient) -> Self pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
where
T: NymConfig,
{
Self { Self {
version: config.get_version().to_string(), version: config.client.version.clone(),
id: config.get_id(), id: config.client.id.clone(),
identity_key: address.identity().to_base58_string(), identity_key: address.identity().to_base58_string(),
encryption_key: address.encryption_key().to_base58_string(), encryption_key: address.encryption_key().to_base58_string(),
gateway_id: config.get_gateway_id(), gateway_id: gateway.gateway_id.clone(),
gateway_listener: config.get_gateway_listener(), gateway_listener: gateway.gateway_listener.clone(),
} }
} }
} }
@@ -148,38 +208,164 @@ impl Display for InitResults {
} }
} }
/// Recovers the already present gateway information or attempts to register with new gateway // helpers for error wrapping
/// and stores the newly obtained key async fn _store_gateway_details<D>(
pub async fn get_registered_gateway<S>( details_store: &D,
validator_servers: Vec<Url>, details: &PersistedGatewayDetails,
key_store: &S::KeyStore, ) -> Result<(), ClientCoreError>
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<(GatewayEndpointConfig, ManagedKeys), ClientCoreError>
where where
S: MixnetClientStorage, D: GatewayDetailsStore,
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static, D::StorageError: Send + Sync + 'static,
{
details_store
.store_gateway_details(details)
.await
.map_err(|source| ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
}
async fn _load_gateway_details<D>(
details_store: &D,
) -> Result<PersistedGatewayDetails, ClientCoreError>
where
D: GatewayDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.load_gateway_details()
.await
.map_err(|source| ClientCoreError::UnavailableGatewayDetails {
source: Box::new(source),
})
}
async fn _load_managed_keys<K>(key_store: &K) -> Result<ManagedKeys, ClientCoreError>
where
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
ManagedKeys::try_load(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
fn ensure_valid_details(
details: &PersistedGatewayDetails,
loaded_keys: &ManagedKeys,
) -> Result<(), ClientCoreError> {
if !details.verify(&loaded_keys.must_get_gateway_shared_key()) {
Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: details.details.gateway_id.clone(),
})
} else {
Ok(())
}
}
pub async fn setup_gateway_from<K, D>(
setup: &GatewaySetup,
key_store: &K,
details_store: &D,
overwrite_data: bool,
gateways: Option<&[gateway::Node]>,
) -> Result<InitialisationDetails, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{ {
let mut rng = OsRng; let mut rng = OsRng;
// try load keys // try load gateway details
let loaded_details = _load_gateway_details(details_store).await;
// try load keys and decide what to do based on the GatewaySetup
let mut managed_keys = match ManagedKeys::try_load(key_store).await { let mut managed_keys = match ManagedKeys::try_load(key_store).await {
Ok(loaded_keys) => { Ok(loaded_keys) => {
// if we loaded something and we don't have full gateway details, check if we can overwrite the data match setup {
if let GatewaySetup::Predefined { config } = setup { GatewaySetup::MustLoad => {
// we already have defined gateway details AND a shared key, so nothing more for us to do // get EVERYTHING from the storage
return Ok((config, loaded_keys)); let details = loaded_details?;
} else if overwrite_keys { ensure_valid_details(&details, &loaded_keys)?;
ManagedKeys::generate_new(&mut rng)
} else { // no need to persist anything as we got everything from the storage
return Err(ClientCoreError::ForbiddenKeyOverwrite); return Ok(InitialisationDetails::new(details.into(), loaded_keys));
}
GatewaySetup::Predefined { details } => {
// we already have defined gateway details AND a shared key
ensure_valid_details(details, &loaded_keys)?;
// if nothing was stored or we're allowed to overwrite what's there, just persist the passed data
if overwrite_data || loaded_details.is_err() {
_store_gateway_details(details_store, details).await?;
}
return Ok(InitialisationDetails::new(
details.clone().into(),
loaded_keys,
));
}
GatewaySetup::Specified { gateway_identity } => {
// if that data was already stored...
if let Ok(existing_gateway) = loaded_details {
ensure_valid_details(&existing_gateway, &loaded_keys)?;
if &existing_gateway.details.gateway_id != gateway_identity
&& !overwrite_data
{
// if our loaded details don't match requested value and we CANT overwrite it...
return Err(ClientCoreError::UnexpectedGatewayDetails);
} else if &existing_gateway.details.gateway_id == gateway_identity {
// if they do match up, just return it
return Ok(InitialisationDetails::new(
existing_gateway.into(),
loaded_keys,
));
}
}
// we didn't get full details from the store and we have loaded some keys
// so we can only continue if we're allowed to overwrite keys
if overwrite_data {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
GatewaySetup::New { .. } => {
if let Ok(existing_gateway) = loaded_details {
ensure_valid_details(&existing_gateway, &loaded_keys)?;
return Ok(InitialisationDetails::new(
existing_gateway.into(),
loaded_keys,
));
}
// we didn't get full details from the store and we have loaded some keys
// so we can only continue if we're allowed to overwrite keys
if overwrite_data {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
} }
} }
Err(_) => ManagedKeys::generate_new(&mut rng), Err(_) => {
// if we failed to load the keys, ensure we didn't provide gateway details in some form
// (in that case we CAN'T generate new keys
if setup.has_full_details() {
return Err(ClientCoreError::UnavailableSharedKey);
}
ManagedKeys::generate_new(&mut rng)
}
}; };
// choose gateway // choose gateway
let gateway_details = setup.try_get_gateway_details(&validator_servers).await?; let gateway_details = setup.choose_gateway(gateways.unwrap_or_default()).await?;
// get our identity key // get our identity key
let our_identity = managed_keys.identity_keypair(); let our_identity = managed_keys.identity_keypair();
@@ -187,6 +373,9 @@ where
// Establish connection, authenticate and generate keys for talking with the gateway // Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?; let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?;
let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys);
// persist gateway keys
managed_keys managed_keys
.deal_with_gateway_key(shared_keys, key_store) .deal_with_gateway_key(shared_keys, key_store)
.await .await
@@ -194,159 +383,39 @@ where
source: Box::new(source), source: Box::new(source),
})?; })?;
// TODO: here we should be probably persisting gateway details as opposed to returning them // persist gateway config
_store_gateway_details(details_store, &persisted_details).await?;
Ok((gateway_details, managed_keys)) Ok(InitialisationDetails::new(
persisted_details.into(),
managed_keys,
))
} }
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the pub async fn setup_gateway<K, D>(
/// arguments given it will do the sensible thing. Either it will setup: &GatewaySetup,
/// key_store: &K,
/// a. Reuse existing gateway configuration from storage. details_store: &D,
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller overwrite_data: bool,
/// knows what they are doing and that the keys match the requested gateway. validator_servers: Option<&[Url]>,
/// c. Create a new gateway configuration with a newly registered gateway and keys. ) -> Result<InitialisationDetails, ClientCoreError>
pub async fn setup_gateway_from_config<C, T, KSt>(
key_store: &KSt,
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where where
C: NymConfig + ClientCoreConfigTrait, K: KeyStore,
T: NymConfig, D: GatewayDetailsStore,
KSt: KeyStore, K::StorageError: Send + Sync + 'static,
<KSt as KeyStore>::StorageError: Send + Sync + 'static, D::StorageError: Send + Sync + 'static,
{ {
let id = config.get_id();
// If we are not going to register gateway, and an explicitly chosen gateway is not passed in,
// load the existing configuration file
if !register_gateway && user_chosen_gateway_id.is_none() {
eprintln!("Not registering gateway, will reuse existing config and keys");
return load_existing_gateway_config::<C>(&id);
}
let gateway_setup = GatewaySetup::new(
None,
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(by_latency),
);
// Else, we proceed by querying the nym-api
let gateway = gateway_setup
.try_get_gateway_details(&config.get_nym_api_endpoints())
.await?;
log::debug!("Querying gateway gives: {:?}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
// wants to keep the,
if !register_gateway && user_chosen_gateway_id.is_some() {
eprintln!("Using gateway provided by user, keeping existing keys");
return Ok(gateway);
}
let mut rng = OsRng; let mut rng = OsRng;
let mut managed_keys = let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?;
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await;
// Create new keys and derive our identity setup_gateway_from(
let our_identity = managed_keys.identity_keypair(); setup,
key_store,
// Establish connection, authenticate and generate keys for talking with the gateway details_store,
eprintln!("Registering with new gateway"); overwrite_data,
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?; Some(&gateways),
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway)
}
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
pub fn load_existing_gateway_config<T>(id: &str) -> Result<GatewayEndpointConfig, ClientCoreError>
where
T: NymConfig + ClientCoreConfigTrait,
{
T::load_from_file(id)
.map(|existing_config| existing_config.get_gateway_endpoint().clone())
.map_err(|err| {
log::error!(
"Unable to configure gateway: {err}. \n
Seems like the client was already initialized but it was not possible to read \
the existing configuration file. \n
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
removing the existing configuration and starting over."
);
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
})
}
/// Get the full client address from the client keys and the gateway identity
pub fn get_client_address(
key_manager: &KeyManager,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new(
*key_manager.identity_keypair().public_key(),
*key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(),
) )
} .await
pub fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
/// Get the client address by loading the keys from stored files.
// TODO: rethink that sucker
pub fn get_client_address_from_stored_ondisk_keys<T>(
config: &Config<T>,
) -> Result<Recipient, ClientCoreError>
where
T: nym_config::NymConfig,
{
fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
fn load_sphinx_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
let sphinx_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&pathfinder.encryption_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
Ok(sphinx_keypair)
}
let pathfinder = ClientKeyPathfinder::new_from_config(config);
let identity_keypair = load_identity_keys(&pathfinder)?;
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
let client_recipient = Recipient::new(
*identity_keypair.public_key(),
*sphinx_keypair.public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(config.get_gateway_id())?,
);
Ok(client_recipient)
} }
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) { pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
+90 -122
View File
@@ -15,9 +15,10 @@ use std::net::SocketAddr;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::TcpStream; use tokio::net::{TcpStream, UdpSocket};
use tokio::time::sleep; use tokio::time::sleep;
use tokio_util::codec::Framed; use tokio_util::codec::Framed;
use tokio_util::udp::UdpFramed;
pub struct Config { pub struct Config {
initial_reconnection_backoff: Duration, initial_reconnection_backoff: Duration,
@@ -57,136 +58,98 @@ pub trait SendWithoutResponse {
} }
pub struct Client { pub struct Client {
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>, conn_new: Option<mpsc::Sender<(FramedNymPacket, SocketAddr)>>,
config: Config, config: Config,
} }
struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>,
current_reconnection_attempt: Arc<AtomicU32>,
}
impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
ConnectionSender {
channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
}
}
}
impl Client { impl Client {
pub fn new(config: Config) -> Client { pub fn new(config: Config) -> Client {
Client { Client {
conn_new: HashMap::new(), conn_new: None,
config, config,
} }
} }
async fn manage_connection( async fn manage_connection(
address: SocketAddr, address: SocketAddr,
receiver: mpsc::Receiver<FramedNymPacket>, receiver: mpsc::Receiver<(FramedNymPacket, SocketAddr)>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
) { ) {
let connection_fut = TcpStream::connect(address); let socket = match UdpSocket::bind("0.0.0.0:0").await {
Ok(socket) => socket,
let conn = match tokio::time::timeout(connection_timeout, connection_fut).await { Err(err) => {
Ok(stream_res) => match stream_res { error!("Failed to bind to - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?");
Ok(stream) => {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec)
}
Err(err) => {
debug!(
"failed to establish connection to {} (err: {})",
address, err
);
return;
}
},
Err(_) => {
debug!(
"failed to connect to {} within {:?}",
address, connection_timeout
);
// we failed to connect - increase reconnection attempt
current_reconnection.fetch_add(1, Ordering::SeqCst);
return; return;
} }
}; };
let framed_conn = UdpFramed::new(socket, NymCodec);
// Take whatever the receiver channel produces and put it on the connection. // Take whatever the receiver channel produces and put it on the connection.
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
// about neither receiver nor the connection, it doesn't matter which one gets consumed // about neither receiver nor the connection, it doesn't matter which one gets consumed
if let Err(err) = receiver.map(Ok).forward(conn).await { if let Err(err) = receiver.map(Ok).forward(framed_conn).await {
warn!("Failed to forward packets to {} - {err}", address); warn!("Failed to forward packets to {} - {err}", address);
} }
debug!( debug!(
"connection manager to {} is finished. Either the connection failed or mixnet client got dropped", "connection manager is finished. Either the connection failed or mixnet client got dropped"
address
); );
} }
/// If we're trying to reconnect, determine how long we should wait. /// If we're trying to reconnect, determine how long we should wait.
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> { // fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
if current_attempt == 0 { // if current_attempt == 0 {
None // None
} else { // } else {
let exp = 2_u32.checked_pow(current_attempt); // let exp = 2_u32.checked_pow(current_attempt);
let backoff = exp // let backoff = exp
.and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp)) // .and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp))
.unwrap_or(self.config.maximum_reconnection_backoff); // .unwrap_or(self.config.maximum_reconnection_backoff);
Some(std::cmp::min( // Some(std::cmp::min(
backoff, // backoff,
self.config.maximum_reconnection_backoff, // self.config.maximum_reconnection_backoff,
)) // ))
} // }
} // }
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) { fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity // this CAN'T fail because we just created the channel which has a non-zero capacity
if self.config.maximum_connection_buffer_size > 0 { if self.config.maximum_connection_buffer_size > 0 {
sender.try_send(pending_packet).unwrap(); sender.try_send((pending_packet, address.into())).unwrap();
} }
self.conn_new = Some(sender);
// if we already tried to connect to `address` before, grab the current attempt count // if we already tried to connect to `address` before, grab the current attempt count
let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) { // let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) {
existing.channel = sender; // existing.channel = sender;
Arc::clone(&existing.current_reconnection_attempt) // Arc::clone(&existing.current_reconnection_attempt)
} else { // } else {
let new_entry = ConnectionSender::new(sender); // let new_entry = ConnectionSender::new(sender);
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt); // let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
self.conn_new.insert(address, new_entry); // self.conn_new.insert(address, new_entry);
current_attempt // current_attempt
}; // };
// load the actual value. // load the actual value.
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire); // let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
let backoff = self.determine_backoff(reconnection_attempt); // let backoff = self.determine_backoff(reconnection_attempt);
// copy the value before moving into another task // copy the value before moving into another task
let initial_connection_timeout = self.config.initial_connection_timeout; // let initial_connection_timeout = self.config.initial_connection_timeout;
tokio::spawn(async move { tokio::spawn(async move {
// before executing the manager, wait for what was specified, if anything // before executing the manager, wait for what was specified, if anything
if let Some(backoff) = backoff { // if let Some(backoff) = backoff {
trace!("waiting for {:?} before attempting connection", backoff); // trace!("waiting for {:?} before attempting connection", backoff);
sleep(backoff).await; // sleep(backoff).await;
} // }
Self::manage_connection( Self::manage_connection(
address.into(), address.into(),
receiver, receiver,
initial_connection_timeout, //initial_connection_timeout,
&current_reconnection_attempt, //&current_reconnection_attempt,
) )
.await .await
}); });
@@ -204,48 +167,53 @@ impl SendWithoutResponse for Client {
let framed_packet = let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version); FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) { match &self.conn_new {
if let Err(err) = sender.channel.try_send(framed_packet) { Some(sender) => {
if err.is_full() { if let Err(err) = sender.clone().try_send((framed_packet, address.into())) {
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address); if err.is_full() {
// it's not a 'big' error, but we did not manage to send the packet debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// if the queue is full, we can't really do anything but to drop the packet // it's not a 'big' error, but we did not manage to send the packet
Err(io::Error::new( // if the queue is full, we can't really do anything but to drop the packet
io::ErrorKind::WouldBlock, Err(io::Error::new(
"connection queue is full", io::ErrorKind::WouldBlock,
)) "connection queue is full",
} else if err.is_disconnected() { ))
debug!( } else if err.is_disconnected() {
"Connection to {} seems to be dead. attempting to re-establish it...", debug!(
address "Connection to {} seems to be dead. attempting to re-establish it...",
); address
// it's not a 'big' error, but we did not manage to send the packet, but queue );
// it up to send it as soon as the connection is re-established // it's not a 'big' error, but we did not manage to send the packet, but queue
self.make_connection(address, err.into_inner()); // it up to send it as soon as the connection is re-established
Err(io::Error::new( self.make_connection(address, err.into_inner().0);
io::ErrorKind::ConnectionAborted, Err(io::Error::new(
"reconnection attempt is in progress", io::ErrorKind::ConnectionAborted,
)) "reconnection attempt is in progress",
))
} else {
// this can't really happen, but let's safe-guard against it in case something changes in futures library
Err(io::Error::new(
io::ErrorKind::Other,
"unknown connection buffer error",
))
}
} else { } else {
// this can't really happen, but let's safe-guard against it in case something changes in futures library debug!("Sending packet to {:?}", address);
Err(io::Error::new( Ok(())
io::ErrorKind::Other,
"unknown connection buffer error",
))
} }
} else {
Ok(())
} }
} else {
// there was never a connection to begin with None => {
debug!("establishing initial connection to {}", address); // there was never a connection to begin with
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet debug!("establishing initial connection");
// for sending for as soon as the connection is created // it's not a 'big' error, but we did not manage to send the packet, but queue the packet
self.make_connection(address, framed_packet); // for sending for as soon as the connection is created
Err(io::Error::new( self.make_connection(address, framed_packet);
io::ErrorKind::NotConnected, Err(io::Error::new(
"connection is in progress", io::ErrorKind::NotConnected,
)) "connection is in progress",
))
}
} }
} }
} }
@@ -29,6 +29,7 @@ log = { workspace = true }
url = { version = "2.2", features = ["serde"] } url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.24.1", features = ["sync", "time"] } tokio = { version = "1.24.1", features = ["sync", "time"] }
futures = "0.3" futures = "0.3"
openssl = { version = "0.10", features = ["vendored"], optional = true }
nym-coconut-interface = { path = "../../coconut-interface" } nym-coconut-interface = { path = "../../coconut-interface" }
nym-network-defaults = { path = "../../network-defaults" } nym-network-defaults = { path = "../../network-defaults" }
@@ -77,13 +78,14 @@ required-features = ["nyxd-client"]
nyxd-client = [ nyxd-client = [
"async-trait", "async-trait",
"cosmrs", "cosmrs",
"cosmwasm-std",
"cw3", "cw3",
"cw4", "cw4",
"prost",
"flate2", "flate2",
"sha2",
"itertools", "itertools",
"cosmwasm-std", "openssl",
"prost",
"sha2",
"signing" "signing"
] ]
signing = [ signing = [
+6 -3
View File
@@ -7,11 +7,14 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
cfg-if = "1.0.0" dirs = { version = "5.0.1", optional = true }
handlebars = "3.0.1" handlebars = "3.5.5"
log = { workspace = true } log = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
toml = "0.5.6" toml = "0.7.4"
url = "2.2" url = "2.2"
nym-network-defaults = { path = "../network-defaults" } nym-network-defaults = { path = "../network-defaults" }
[features]
default = ["dirs"]
+208
View File
@@ -0,0 +1,208 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_network_defaults::mainnet::read_var_if_not_default;
use nym_network_defaults::var_names::CONFIGURED;
use std::any::type_name;
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
pub const MISSING_VALUE: &str = "MISSING VALUE";
/// Helper for providing default value for templated config fields.
pub fn missing_string_value<T: From<String>>() -> T {
MISSING_VALUE.to_string().into()
}
/// Helper for providing default INADDR_ANY IpAddr, i.e. `0.0.0.0`
pub fn inaddr_any() -> IpAddr {
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
}
/// Helper for providing default IN6ADDR_ANY_INIT IpAddr, i.e. `::`
pub fn in6addr_any_init() -> IpAddr {
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
}
/// Helper for providing binding warnings if node tries to bind to any of those
pub const SPECIAL_ADDRESSES: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::LOCALHOST),
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
IpAddr::V4(Ipv4Addr::BROADCAST),
IpAddr::V6(Ipv6Addr::LOCALHOST),
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
];
// TODO: is it really part of 'Config'?
pub trait OptionalSet {
/// If the value is available (i.e. `Some`), the provided closure is applied.
/// Otherwise `self` is returned with no modifications.
fn with_optional<F, T>(self, f: F, val: Option<T>) -> Self
where
F: Fn(Self, T) -> Self,
Self: Sized,
{
if let Some(val) = val {
f(self, val)
} else {
self
}
}
/// If the value is available (i.e. `Some`) it is validated and then the provided closure is applied.
/// Otherwise `self` is returned with no modifications.
fn with_validated_optional<F, T, V, E>(
self,
f: F,
value: Option<T>,
validate: V,
) -> Result<Self, E>
where
F: Fn(Self, T) -> Self,
V: Fn(&T) -> Result<(), E>,
Self: Sized,
{
if let Some(val) = value {
validate(&val)?;
Ok(f(self, val))
} else {
Ok(self)
}
}
/// If the value is available (i.e. `Some`), the provided closure is applied.
/// Otherwise, if the environment was configured and the corresponding variable was set,
/// the value is parsed using the `FromStr` implementation and the closure is applied on that instead.
/// Finally, if none of those were available, `self` is returned with no modifications.
fn with_optional_env<F, T>(self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(Self, T) -> Self,
T: FromStr,
<T as FromStr>::Err: Debug,
Self: Sized,
{
if let Some(val) = val {
return f(self, val);
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw) = read_var_if_not_default(env_var) {
return f(
self,
raw.parse().unwrap_or_else(|err| {
panic!(
"failed to parse value of {raw} into type {}. the error was {:?}",
type_name::<T>(),
err
)
}),
);
}
}
self
}
/// If the value is available (i.e. `Some`), the provided closure is applied.
/// Otherwise, if the environment was configured and the corresponding variable was set,
/// the value is parsed using the provided parser and the closure is applied on that instead.
/// Finally, if none of those were available, `self` is returned with no modifications.
fn with_optional_custom_env<F, T, G>(
self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn(Self, T) -> Self,
G: Fn(&str) -> T,
Self: Sized,
{
if let Some(val) = val {
return f(self, val);
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw) = read_var_if_not_default(env_var) {
return f(self, parser(&raw));
}
}
self
}
}
// helper for when we want to use `OptionalSet` on an inner field
// (used by clients wanting to set the `BaseConfig` values)
#[macro_export]
macro_rules! define_optional_set_inner {
( $x: ident, $inner_field_name: ident, $inner_field_typ: ty ) => {
impl $x {
pub fn with_optional_inner<F, T>(mut self, f: F, val: Option<T>) -> Self
where
F: Fn($inner_field_typ, T) -> $inner_field_typ,
{
self.$inner_field_name = self.$inner_field_name.with_optional(f, val);
self
}
pub fn with_validated_optional_inner<F, T, V, E>(
mut self,
f: F,
value: Option<T>,
validate: V,
) -> Result<Self, E>
where
F: Fn($inner_field_typ, T) -> $inner_field_typ,
V: Fn(&T) -> Result<(), E>,
{
self.$inner_field_name = self
.$inner_field_name
.with_validated_optional(f, value, validate)?;
Ok(self)
}
pub fn with_optional_env_inner<F, T>(
mut self,
f: F,
val: Option<T>,
env_var: &str,
) -> Self
where
F: Fn($inner_field_typ, T) -> $inner_field_typ,
T: FromStr,
<T as FromStr>::Err: Debug,
{
self.$inner_field_name = self.$inner_field_name.with_optional_env(f, val, env_var);
self
}
pub fn with_optional_custom_env_inner<F, T, G>(
mut self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn($inner_field_typ, T) -> $inner_field_typ,
G: Fn(&str) -> T,
{
self.$inner_field_name = self
.$inner_field_name
.with_optional_custom_env(f, val, env_var, parser);
self
}
}
};
}
// this function is only used for parsing values from the network defaults and thus the "expect" there are fine
pub fn parse_urls(raw: &str) -> Vec<url::Url> {
raw.split(',')
.map(|raw_url| {
raw_url
.trim()
.parse()
.expect("one of the provided urls was invalid")
})
.collect()
}
impl<T> OptionalSet for T {}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// removed in 1.1.19/1.1.20
pub mod nym_config {
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::{fs, io};
pub const CONFIG_DIR: &str = "config";
pub const DATA_DIR: &str = "data";
// no need for anything to do with saving.
pub trait MigrationNymConfig: Serialize + DeserializeOwned {
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf;
fn default_data_directory(id: &str) -> PathBuf {
Self::default_data_directory_with_root(Self::default_root_directory(), id)
}
fn default_data_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
root.as_ref().join(id).join(DATA_DIR)
}
fn default_config_directory(id: &str) -> PathBuf {
Self::default_config_directory_with_root(Self::default_root_directory(), id)
}
fn default_config_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
root.as_ref().join(id).join(CONFIG_DIR)
}
fn default_config_file_path(id: &str) -> PathBuf {
Self::default_config_directory(id).join(Self::config_file_name())
}
fn load_from_file(id: &str) -> io::Result<Self> {
let file = Self::default_config_file_path(id);
Self::load_from_filepath(file)
}
fn load_from_filepath<P: AsRef<Path>>(filepath: P) -> io::Result<Self> {
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
let config_contents = fs::read_to_string(filepath)?;
toml::from_str(&config_contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
}
}
}
+172 -192
View File
@@ -1,219 +1,199 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use handlebars::Handlebars; use handlebars::{Handlebars, TemplateRenderError};
use nym_network_defaults::mainnet::read_var_if_not_default;
use nym_network_defaults::var_names::CONFIGURED;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use std::any::type_name; use std::fs::File;
use std::fmt::Debug; use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fs, io}; use std::{fs, io};
pub use helpers::{parse_urls, OptionalSet};
pub use toml::de::Error as TomlDeError;
pub mod defaults; pub mod defaults;
pub mod helpers;
pub mod legacy_helpers;
pub const CONFIG_DIR: &str = "config"; pub const NYM_DIR: &str = ".nym";
pub const DATA_DIR: &str = "data"; pub const DEFAULT_CONFIG_DIR: &str = "config";
pub const CRED_DB_FILE_NAME: &str = "credentials_database.db"; pub const DEFAULT_DATA_DIR: &str = "data";
pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml";
pub trait NymConfig: Default + Serialize + DeserializeOwned { #[cfg(feature = "dirs")]
pub fn must_get_home() -> PathBuf {
dirs::home_dir().expect("Failed to evaluate $HOME value")
}
#[cfg(feature = "dirs")]
pub fn may_get_home() -> Option<PathBuf> {
dirs::home_dir()
}
pub trait NymConfigTemplate: Serialize {
fn template() -> &'static str; fn template() -> &'static str;
fn config_file_name() -> String { fn format_to_string(&self) -> String {
"config.toml".to_string() // it is responsibility of whoever is implementing the trait to ensure the template is valid
Handlebars::new()
.render_template(Self::template(), &self)
.unwrap()
} }
fn default_root_directory() -> PathBuf; fn format_to_writer<W: Write>(&self, writer: W) -> io::Result<()> {
if let Err(err) =
// default, most probable, implementations; can be easily overridden where required Handlebars::new().render_template_to_write(Self::template(), &self, writer)
fn default_config_directory(id: &str) -> PathBuf { {
Self::default_config_directory_with_root(Self::default_root_directory(), id) match err {
} TemplateRenderError::IOError(err, _) => return Err(err),
other_err => {
fn default_config_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf { // it is responsibility of whoever is implementing the trait to ensure the template is valid
root.as_ref().join(id).join(CONFIG_DIR) panic!("invalid template: {other_err}")
}
fn default_data_directory(id: &str) -> PathBuf {
Self::default_data_directory_with_root(Self::default_root_directory(), id)
}
fn default_data_directory_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
root.as_ref().join(id).join(DATA_DIR)
}
fn default_config_file_path(id: &str) -> PathBuf {
Self::default_config_directory(id).join(Self::config_file_name())
}
fn default_config_file_path_with_root<P: AsRef<Path>>(root: P, id: &str) -> PathBuf {
Self::default_config_directory_with_root(root, id).join(Self::config_file_name())
}
// We provide a second set of functions that tries to not panic.
fn try_default_root_directory() -> Option<PathBuf>;
fn try_default_config_directory(id: &str) -> Option<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
}
fn try_default_data_directory(id: &str) -> Option<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
}
fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
}
fn root_directory(&self) -> PathBuf;
fn config_directory(&self) -> PathBuf;
fn data_directory(&self) -> PathBuf;
fn save_to_file(&self, custom_location: Option<PathBuf>) -> io::Result<()> {
let reg = Handlebars::new();
// it's whoever is implementing the trait responsibility to make sure you can execute your own template on your data
let templated_config = reg.render_template(Self::template(), self).unwrap();
// make sure the whole directory structure actually exists
match custom_location.clone() {
Some(loc) => {
if let Some(parent_dir) = loc.parent() {
fs::create_dir_all(parent_dir)
} else {
Ok(())
} }
} }
None => fs::create_dir_all(self.config_directory()),
}?;
let location = custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
log::info!("Configuration file will be saved to {:?}", location);
cfg_if::cfg_if! {
if #[cfg(unix)] {
fs::write(location.clone(), templated_config)?;
let mut perms = fs::metadata(location.clone())?.permissions();
perms.set_mode(0o600);
fs::set_permissions(location, perms)?;
} else {
fs::write(location, templated_config)?;
}
} }
Ok(()) Ok(())
} }
fn load_from_file(id: &str) -> io::Result<Self> {
let file = Self::default_config_file_path(id);
Self::load_from_filepath(file)
}
fn load_from_filepath<P: AsRef<Path>>(filepath: P) -> io::Result<Self> {
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
let config_contents = fs::read_to_string(filepath)?;
toml::from_str(&config_contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
}
} }
// this function is only used for parsing values from the network defaults and thus the "expect" there are fine pub fn save_formatted_config_to_file<C, P>(config: &C, path: P) -> io::Result<()>
pub fn parse_urls(raw: &str) -> Vec<url::Url> { where
raw.split(',') C: NymConfigTemplate,
.map(|raw_url| { P: AsRef<Path>,
raw_url {
.trim() log::trace!("trying to save config file to {}", path.as_ref().display());
.parse() let file = File::create(path.as_ref())?;
.expect("one of the provided nym api urls is invalid")
}) // TODO: check for whether any of our configs stores anything sensitive
.collect() // and change that to 0o644 instead
#[cfg(target_family = "unix")]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path.as_ref())?.permissions();
perms.set_mode(0o600);
fs::set_permissions(path, perms)?;
}
config.format_to_writer(file)
} }
pub trait OptionalSet { pub fn deserialize_config_from_toml_str<C>(raw: &str) -> Result<C, TomlDeError>
fn with_optional<F, T>(self, f: F, val: Option<T>) -> Self where
where C: DeserializeOwned,
F: Fn(Self, T) -> Self, {
Self: Sized, toml::from_str(raw)
{
if let Some(val) = val {
f(self, val)
} else {
self
}
}
fn with_validated_optional<F, T, V, E>(
self,
f: F,
value: Option<T>,
validate: V,
) -> Result<Self, E>
where
F: Fn(Self, T) -> Self,
V: Fn(&T) -> Result<(), E>,
Self: Sized,
{
if let Some(val) = value {
validate(&val)?;
Ok(f(self, val))
} else {
Ok(self)
}
}
fn with_optional_env<F, T>(self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(Self, T) -> Self,
T: FromStr,
<T as FromStr>::Err: Debug,
Self: Sized,
{
if let Some(val) = val {
return f(self, val);
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw) = read_var_if_not_default(env_var) {
return f(
self,
raw.parse().unwrap_or_else(|err| {
panic!(
"failed to parse value of {raw} into type {}. the error was {:?}",
type_name::<T>(),
err
)
}),
);
}
}
self
}
fn with_optional_custom_env<F, T, G>(
self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn(Self, T) -> Self,
G: Fn(&str) -> T,
Self: Sized,
{
if let Some(val) = val {
return f(self, val);
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw) = read_var_if_not_default(env_var) {
return f(self, parser(&raw));
}
}
self
}
} }
impl<T> OptionalSet for T where T: NymConfig {} pub fn read_config_from_toml_file<C, P>(path: P) -> io::Result<C>
where
C: DeserializeOwned,
P: AsRef<Path>,
{
log::trace!(
"trying to read config file from {}",
path.as_ref().display()
);
let content = fs::read_to_string(path)?;
// TODO: should we be preserving original error type instead?
deserialize_config_from_toml_str(&content)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
}
//
//
//
// pub trait NymConfig: Default + Serialize + DeserializeOwned {
// fn template() -> &'static str;
//
// fn config_file_name() -> String {
// "config.toml".to_string()
// }
//
// fn default_root_directory() -> PathBuf;
//
// // default, most probable, implementations; can be easily overridden where required
// fn default_config_directory(id: &str) -> PathBuf {
// Self::default_root_directory()
// .join(id)
// .join(DEFAULT_CONFIG_DIR)
// }
//
// fn default_data_directory(id: &str) -> PathBuf {
// Self::default_root_directory()
// .join(id)
// .join(DEFAULT_DATA_DIR)
// }
//
// fn default_config_file_path(id: &str) -> PathBuf {
// Self::default_config_directory(id).join(Self::config_file_name())
// }
//
// // We provide a second set of functions that tries to not panic.
//
// fn try_default_root_directory() -> Option<PathBuf>;
//
// fn try_default_config_directory(id: &str) -> Option<PathBuf> {
// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_CONFIG_DIR))
// }
//
// fn try_default_data_directory(id: &str) -> Option<PathBuf> {
// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_DATA_DIR))
// }
//
// fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
// Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
// }
//
// fn root_directory(&self) -> PathBuf;
// fn config_directory(&self) -> PathBuf;
// fn data_directory(&self) -> PathBuf;
//
// fn save_to_file(&self, custom_location: Option<PathBuf>) -> io::Result<()> {
// Ok(())
// // let reg = Handlebars::new();
// // // it's whoever is implementing the trait responsibility to make sure you can execute your own template on your data
// // let templated_config = reg.render_template(Self::template(), self).unwrap();
// //
// // // make sure the whole directory structure actually exists
// // match custom_location.clone() {
// // Some(loc) => {
// // if let Some(parent_dir) = loc.parent() {
// // fs::create_dir_all(parent_dir)
// // } else {
// // Ok(())
// // }
// // }
// // None => fs::create_dir_all(self.config_directory()),
// // }?;
// //
// // let location = custom_location
// // .unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
// // log::info!("Configuration file will be saved to {:?}", location);
// //
// // cfg_if::cfg_if! {
// // if #[cfg(unix)] {
// // fs::write(location.clone(), templated_config)?;
// // let mut perms = fs::metadata(location.clone())?.permissions();
// // perms.set_mode(0o600);
// // fs::set_permissions(location, perms)?;
// // } else {
// // fs::write(location, templated_config)?;
// // }
// // }
// //
// // Ok(())
// }
//
// fn load_from_file(id: &str) -> io::Result<Self> {
// let file = Self::default_config_file_path(id);
// log::trace!("Loading from file: {:#?}", file);
// let config_contents = fs::read_to_string(file)?;
//
// toml::from_str(&config_contents)
// .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
// }
// }
+4 -1
View File
@@ -4,8 +4,11 @@
*/ */
use crate::ephemeral_storage::EphemeralStorage; use crate::ephemeral_storage::EphemeralStorage;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::persistent_storage::PersistentStorage; use crate::persistent_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
mod backends; mod backends;
pub mod ephemeral_storage; pub mod ephemeral_storage;
@@ -16,7 +19,7 @@ pub mod persistent_storage;
pub mod storage; pub mod storage;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub async fn initialise_persistent_storage(path: std::path::PathBuf) -> PersistentStorage { pub async fn initialise_persistent_storage<P: AsRef<Path>>(path: P) -> PersistentStorage {
match persistent_storage::PersistentStorage::init(path).await { match persistent_storage::PersistentStorage::init(path).await {
Err(err) => panic!("failed to initialise credential storage - {err}"), Err(err) => panic!("failed to initialise credential storage - {err}"),
Ok(storage) => storage, Ok(storage) => storage,
@@ -23,7 +23,7 @@ impl PersistentStorage {
/// # Arguments /// # Arguments
/// ///
/// * `database_path`: path to the database. /// * `database_path`: path to the database.
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StorageError> { pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
debug!( debug!(
"Attempting to connect to database {:?}", "Attempting to connect to database {:?}",
database_path.as_ref().as_os_str() database_path.as_ref().as_os_str()
+14 -14
View File
@@ -19,20 +19,20 @@ macro_rules! measure {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(feature = "cpucycles")] { if #[cfg(feature = "cpucycles")] {
let start_cycles = $crate::cpu_cycles(); let start_cycles = $crate::cpu_cycles();
// if the block needs to return something, we can return it // if the block needs to return something, we can return it
let r = $x; let r = $x;
let end_cycles = $crate::cpu_cycles(); let end_cycles = $crate::cpu_cycles();
let name = if let Some(meta) = tracing::Span::current().metadata() { let name = if let Some(meta) = tracing::Span::current().metadata() {
meta.name() meta.name()
} else { } else {
"measure" "measure"
}; };
match (start_cycles, end_cycles) { match (start_cycles, end_cycles) {
(Ok(start), Ok(end)) => info!("{} cpucycles: {}", name, end - start), (Ok(start), Ok(end)) => log::trace!("{} cpucycles: {}", name, end - start),
(Err(e), _) => error!("{e}"), (Err(e), _) => error!("{e}"),
(_, Err(e)) => error!("{e}"), (_, Err(e)) => error!("{e}"),
} }
r r
} else { } else {
$x $x
} }
-549
View File
@@ -1,549 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// This should be modified whenever an updated Ethereum contract is uploaded
pub const ETH_JSON_ABI: &str = r#"
[
{
"inputs": [
{
"internalType": "contract CosmosERC20",
"name": "_erc20",
"type": "address"
},
{
"internalType": "contract Gravity",
"name": "_gravityBridge",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "Bandwidth",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "VerificationKey",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "SignedVerificationKey",
"type": "bytes"
},
{
"indexed": false,
"internalType": "string",
"name": "CosmosRecipient",
"type": "string"
}
],
"name": "BBCredentialPurchased",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "Enabled",
"type": "bool"
}
],
"name": "CredentialGenerationSwitch",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "NewBytesPerToken",
"type": "uint256"
}
],
"name": "RatioChanged",
"type": "event"
},
{
"inputs": [],
"name": "BytesPerToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "bandwidthFromToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_newBytesPerTokenAmount",
"type": "uint256"
}
],
"name": "changeRatio",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "credentialGenerationEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "_generation",
"type": "bool"
}
],
"name": "credentialGenerationSwitch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "erc20",
"outputs": [
{
"internalType": "contract CosmosERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_verificationKey",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_signedVerificationKey",
"type": "bytes"
},
{
"internalType": "string",
"name": "_cosmosRecipient",
"type": "string"
}
],
"name": "generateBasicBandwidthCredential",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "gravityBridge",
"outputs": [
{
"internalType": "contract Gravity",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
"#;
pub const ETH_ERC20_JSON_ABI: &str = r#"
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
"#;
@@ -58,6 +58,7 @@ impl SurbAck {
let packet_size = match packet_type { let packet_size = match packet_type {
PacketType::Outfox => surb_ack_payload.len().max(MIN_PACKET_SIZE), PacketType::Outfox => surb_ack_payload.len().max(MIN_PACKET_SIZE),
PacketType::Mix => PacketSize::AckPacket.payload_size(), PacketType::Mix => PacketSize::AckPacket.payload_size(),
#[allow(deprecated)]
PacketType::Vpn => PacketSize::AckPacket.payload_size(), PacketType::Vpn => PacketSize::AckPacket.payload_size(),
}; };
@@ -75,6 +76,7 @@ impl SurbAck {
&destination, &destination,
&delays, &delays,
)?, )?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build( PacketType::Vpn => NymPacket::sphinx_build(
packet_size, packet_size,
surb_ack_payload, surb_ack_payload,
@@ -105,6 +107,7 @@ impl SurbAck {
PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN
} }
PacketType::Mix => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN, PacketType::Mix => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
#[allow(deprecated)]
PacketType::Vpn => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN, PacketType::Vpn => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN,
} }
} }
@@ -137,6 +140,7 @@ impl SurbAck {
let packet = match packet_type { let packet = match packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(&b[address_offset..])?, PacketType::Outfox => NymPacket::outfox_from_bytes(&b[address_offset..])?,
PacketType::Mix => NymPacket::sphinx_from_bytes(&b[address_offset..])?, PacketType::Mix => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_from_bytes(&b[address_offset..])?, PacketType::Vpn => NymPacket::sphinx_from_bytes(&b[address_offset..])?,
}; };
+1
View File
@@ -85,6 +85,7 @@ impl Decoder for NymCodec {
match header.packet_type { match header.packet_type {
PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?, PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?,
PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?, PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?, PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?,
} }
} else { } else {
+2 -1
View File
@@ -26,7 +26,7 @@ const ACK_IV_SIZE: usize = 16;
const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD; const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD;
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD; const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_8: usize = 1 * 512 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD; const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD;
@@ -244,6 +244,7 @@ impl PacketSize {
) -> Result<Self, InvalidPacketSize> { ) -> Result<Self, InvalidPacketSize> {
let overhead = match packet_type { let overhead = match packet_type {
PacketType::Mix => SPHINX_PACKET_OVERHEAD, PacketType::Mix => SPHINX_PACKET_OVERHEAD,
#[allow(deprecated)]
PacketType::Vpn => SPHINX_PACKET_OVERHEAD, PacketType::Vpn => SPHINX_PACKET_OVERHEAD,
PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, PacketType::Outfox => OUTFOX_PACKET_OVERHEAD,
}; };
+11 -2
View File
@@ -1,13 +1,15 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![allow(deprecated)]
// allow the u8 repr of `Vpn` PacketType whilst deprecating all of its other uses
use crate::PacketSize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
use thiserror::Error; use thiserror::Error;
use crate::PacketSize;
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("{received} is not a valid packet mode tag")] #[error("{received} is not a valid packet mode tag")]
pub struct InvalidPacketType { pub struct InvalidPacketType {
@@ -15,17 +17,23 @@ pub struct InvalidPacketType {
} }
#[repr(u8)] #[repr(u8)]
#[allow(deprecated)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PacketType { pub enum PacketType {
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate /// Represents 'normal' packet sent through the network that should be delayed by an appropriate
/// value at each hop. /// value at each hop.
#[default] #[default]
#[serde(rename = "mix")]
#[serde(alias = "sphinx")]
Mix = 0, Mix = 0,
/// Represents a packet that should be sent through the network as fast as possible. /// Represents a packet that should be sent through the network as fast as possible.
#[deprecated]
#[serde(rename = "unsupported-mix-vpn")]
Vpn = 1, Vpn = 1,
/// Abusing this to add Outfox support /// Abusing this to add Outfox support
#[serde(rename = "outfox")]
Outfox = 2, Outfox = 2,
} }
@@ -33,6 +41,7 @@ impl fmt::Display for PacketType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
PacketType::Mix => write!(f, "Mix"), PacketType::Mix => write!(f, "Mix"),
#[allow(deprecated)]
PacketType::Vpn => write!(f, "Vpn"), PacketType::Vpn => write!(f, "Vpn"),
PacketType::Outfox => write!(f, "Outfox"), PacketType::Outfox => write!(f, "Outfox"),
} }
+1
View File
@@ -251,6 +251,7 @@ pub trait FragmentPreparer {
&destination, &destination,
&delays, &delays,
)?, )?,
#[allow(deprecated)]
PacketType::Vpn => NymPacket::sphinx_build( PacketType::Vpn => NymPacket::sphinx_build(
packet_size.payload_size(), packet_size.payload_size(),
packet_payload, packet_payload,
+35 -168
View File
@@ -1,88 +1,41 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::config::template::config_template; pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_client_core::config::Config as BaseConfig;
pub use nym_client_core::config::MISSING_VALUE;
use nym_client_core::config::{ClientCoreConfigTrait, DebugConfig};
use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet}; use nym_config::OptionalSet;
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::Socks5ProtocolVersion;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
pub mod old_config_v1_1_13; pub mod old_config_v1_1_20_2;
mod template;
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
const DEFAULT_CONNECTION_START_SURBS: u32 = 20; const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
const DEFAULT_PER_REQUEST_SURBS: u32 = 3; const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Config { pub struct Config {
#[serde(flatten)] #[serde(flatten)]
base: BaseConfig<Config>, pub base: BaseClientConfig,
socks5: Socks5, pub socks5: Socks5,
}
impl NymConfig for Config {
fn template() -> &'static str {
config_template()
}
fn default_root_directory() -> PathBuf {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value");
#[cfg(any(target_os = "android", target_os = "ios"))]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.get_nym_root_directory()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(self.base.get_id())
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join(self.base.get_id()).join("data")
}
}
impl ClientCoreConfigTrait for Config {
fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig {
self.base.get_gateway_endpoint()
}
} }
impl Config { impl Config {
pub fn new<S: Into<String>>(id: S, provider_mix_address: S) -> Self { pub fn new<S: Into<String>>(id: S, version: S, provider_mix_address: S) -> Self {
Config { Config {
base: BaseConfig::new(id), base: BaseClientConfig::new(id, version),
socks5: Socks5::new(provider_mix_address), socks5: Socks5::new(provider_mix_address),
} }
} }
#[must_use] pub fn from_base(base: BaseClientConfig, socks5: Socks5) -> Self {
pub fn with_root_directory<P: AsRef<Path>>(mut self, root_dir: P) -> Self { Config { base, socks5 }
self.base = self.base.reset_nym_root_directory(root_dir);
let data_dir = self.data_directory();
self.base = self.base.reset_data_directory(data_dir);
self
} }
pub fn validate(&self) -> bool { pub fn validate(&self) -> bool {
@@ -90,63 +43,38 @@ impl Config {
self.base.validate() self.base.validate()
} }
// getters pub fn with_port(mut self, port: u16) -> Self {
pub fn get_base(&self) -> &BaseConfig<Self> { self.socks5.listening_port = port;
&self.base self
} }
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> { pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
&mut self.base self.socks5.send_anonymously = anonymous_replies;
} self
pub fn get_socks5(&self) -> &Socks5 {
&self.socks5
}
pub fn get_socks5_mut(&mut self) -> &mut Socks5 {
&mut self.socks5
}
pub fn get_debug_settings(&self) -> &DebugConfig {
self.get_base().get_debug_config()
}
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
} }
// poor man's 'builder' method // poor man's 'builder' method
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{ {
self.base = f(self.base, val); self.base = f(self.base, val);
self self
} }
pub fn with_port(mut self, port: u16) -> Self {
self.socks5.with_port(port);
self
}
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
self.socks5.with_anonymous_replies(anonymous_replies);
self
}
// helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config
// (plz, lets refactor it) // (plz, lets refactor it)
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{ {
self.base = self.base.with_optional(f, val); self.base = self.base.with_optional(f, val);
self self
} }
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr, T: FromStr,
<T as FromStr>::Err: Debug, <T as FromStr>::Err: Debug,
{ {
@@ -154,7 +82,7 @@ impl Config {
self self
} }
pub fn with_optional_custom_env_ext<F, T, G>( pub fn with_optional_base_custom_env<F, T, G>(
mut self, mut self,
f: F, f: F,
val: Option<T>, val: Option<T>,
@@ -162,7 +90,7 @@ impl Config {
parser: G, parser: G,
) -> Self ) -> Self
where where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>, F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T, G: Fn(&str) -> T,
{ {
self.base = self.base.with_optional_custom_env(f, val, env_var, parser); self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
@@ -174,19 +102,19 @@ impl Config {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Socks5 { pub struct Socks5 {
/// The port on which the client will be listening for incoming requests /// The port on which the client will be listening for incoming requests
listening_port: u16, pub listening_port: u16,
/// The mix address of the provider to which all requests are going to be sent. /// The mix address of the provider to which all requests are going to be sent.
provider_mix_address: String, pub provider_mix_address: String,
/// The version of the 'service provider' this client is going to use in its communication with the /// The version of the 'service provider' this client is going to use in its communication with the
/// specified socks5 provider. /// specified socks5 provider.
// if in doubt, use the legacy version as initially nobody will be using the updated binaries // if in doubt, use the legacy version as initially nobody will be using the updated binaries
#[serde(default = "ProviderInterfaceVersion::new_legacy")] #[serde(default = "ProviderInterfaceVersion::new_legacy")]
provider_interface_version: ProviderInterfaceVersion, pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")] #[serde(default = "Socks5ProtocolVersion::new_legacy")]
socks5_protocol_version: Socks5ProtocolVersion, pub socks5_protocol_version: Socks5ProtocolVersion,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
/// While this is going to hide its actual address information, it will make the actual communication /// While this is going to hide its actual address information, it will make the actual communication
@@ -194,10 +122,10 @@ pub struct Socks5 {
/// ///
/// Note that some service providers might not support this. /// Note that some service providers might not support this.
#[serde(default)] #[serde(default)]
send_anonymously: bool, pub send_anonymously: bool,
#[serde(default)] #[serde(default)]
socks5_debug: Socks5Debug, pub socks5_debug: Socks5Debug,
} }
impl Socks5 { impl Socks5 {
@@ -212,81 +140,20 @@ impl Socks5 {
} }
} }
pub fn with_port(&mut self, port: u16) {
self.listening_port = port;
}
pub fn with_provider_mix_address(&mut self, address: String) {
self.provider_mix_address = address;
}
pub fn with_provider_interface_version(&mut self, version: ProviderInterfaceVersion) {
self.provider_interface_version = version;
}
pub fn with_socks5_protocol_version(&mut self, version: Socks5ProtocolVersion) {
self.socks5_protocol_version = version;
}
pub fn with_anonymous_replies(&mut self, anonymous_replies: bool) {
self.send_anonymously = anonymous_replies;
}
pub fn get_raw_provider_mix_address(&self) -> String {
self.provider_mix_address.clone()
}
pub fn get_provider_mix_address(&self) -> Recipient { pub fn get_provider_mix_address(&self) -> Recipient {
Recipient::try_from_base58_string(&self.provider_mix_address) Recipient::try_from_base58_string(&self.provider_mix_address)
.expect("malformed provider address") .expect("malformed provider address")
} }
pub fn get_provider_interface_version(&self) -> ProviderInterfaceVersion {
self.provider_interface_version
}
pub fn get_socks5_protocol_version(&self) -> Socks5ProtocolVersion {
self.socks5_protocol_version
}
pub fn get_send_anonymously(&self) -> bool {
self.send_anonymously
}
pub fn get_listening_port(&self) -> u16 {
self.listening_port
}
pub fn get_connection_start_surbs(&self) -> u32 {
self.socks5_debug.connection_start_surbs
}
pub fn get_per_request_surbs(&self) -> u32 {
self.socks5_debug.per_request_surbs
}
} }
impl Default for Socks5 { #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
fn default() -> Self {
Socks5 {
listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
provider_mix_address: "".into(),
provider_interface_version: ProviderInterfaceVersion::Legacy,
socks5_protocol_version: Socks5ProtocolVersion::Legacy,
send_anonymously: false,
socks5_debug: Default::default(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Socks5Debug { pub struct Socks5Debug {
/// Number of reply SURBs attached to each `Request::Connect` message. /// Number of reply SURBs attached to each `Request::Connect` message.
connection_start_surbs: u32, pub connection_start_surbs: u32,
/// Number of reply SURBs attached to each `Request::Send` message. /// Number of reply SURBs attached to each `Request::Send` message.
per_request_surbs: u32, pub per_request_surbs: u32,
} }
impl Default for Socks5Debug { impl Default for Socks5Debug {
@@ -3,60 +3,60 @@
use crate::config::{Config, Socks5}; use crate::config::{Config, Socks5};
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::NymConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] // #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] // #[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 { // pub struct OldConfigV1_1_13 {
#[serde(flatten)] // #[serde(flatten)]
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>, // base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
//
socks5: Socks5, // socks5: Socks5,
} // }
//
impl NymConfig for OldConfigV1_1_13 { // //
fn template() -> &'static str { // // impl NymConfig for OldConfigV1_1_13 {
// not intended to be used // // fn template() -> &'static str {
unimplemented!() // // // not intended to be used
} // // unimplemented!()
// // }
fn default_root_directory() -> PathBuf { // //
#[cfg(not(any(target_os = "android", target_os = "ios")))] // // fn default_root_directory() -> PathBuf {
let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value"); // // #[cfg(not(target_os = "android"))]
#[cfg(any(target_os = "android", target_os = "ios"))] // // let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value");
let base_dir = PathBuf::from("/tmp"); // // #[cfg(target_os = "android")]
// // let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients") // //
} // // base_dir.join(".nym").join("socks5-clients")
// // }
fn try_default_root_directory() -> Option<PathBuf> { // //
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients")) // // fn try_default_root_directory() -> Option<PathBuf> {
} // // dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
// // }
fn root_directory(&self) -> PathBuf { // //
self.base.client.nym_root_directory.clone() // // fn root_directory(&self) -> PathBuf {
} // // self.base.client.nym_root_directory.clone()
// // }
fn config_directory(&self) -> PathBuf { // //
self.root_directory() // // fn config_directory(&self) -> PathBuf {
.join(&self.base.client.id) // // self.root_directory()
.join("config") // // .join(&self.base.client.id)
} // // .join("config")
// // }
fn data_directory(&self) -> PathBuf { // //
self.root_directory() // // fn data_directory(&self) -> PathBuf {
.join(&self.base.client.id) // // self.root_directory()
.join("data") // // .join(&self.base.client.id)
} // // .join("data")
} // // }
// // }
impl From<OldConfigV1_1_13> for Config { //
fn from(value: OldConfigV1_1_13) -> Self { // impl From<OldConfigV1_1_13> for Config {
Config { // fn from(value: OldConfigV1_1_13) -> Self {
base: value.base.into(), // Config {
socks5: value.socks5, // base: value.base.into(),
} // socks5: value.socks5,
} // }
} // }
// }
@@ -0,0 +1,87 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::config::{Config, Socks5, Socks5Debug};
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
#[serde(flatten)]
pub base: BaseClientConfigV1_1_20_2,
pub socks5: Socks5V1_1_20_2,
}
impl From<ConfigV1_1_20_2> for Config {
fn from(value: ConfigV1_1_20_2) -> Self {
Config {
base: value.base.into(),
socks5: value.socks5.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5V1_1_20_2 {
pub listening_port: u16,
pub provider_mix_address: String,
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
pub socks5_protocol_version: Socks5ProtocolVersion,
#[serde(default)]
pub send_anonymously: bool,
#[serde(default)]
pub socks5_debug: Socks5DebugV1_1_20_2,
}
impl From<Socks5V1_1_20_2> for Socks5 {
fn from(value: Socks5V1_1_20_2) -> Self {
Socks5 {
listening_port: value.listening_port,
provider_mix_address: value.provider_mix_address,
provider_interface_version: value.provider_interface_version,
socks5_protocol_version: value.socks5_protocol_version,
send_anonymously: value.send_anonymously,
socks5_debug: value.socks5_debug.into(),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5DebugV1_1_20_2 {
/// Number of reply SURBs attached to each `Request::Connect` message.
pub connection_start_surbs: u32,
/// Number of reply SURBs attached to each `Request::Send` message.
pub per_request_surbs: u32,
}
impl From<Socks5DebugV1_1_20_2> for Socks5Debug {
fn from(value: Socks5DebugV1_1_20_2) -> Self {
Socks5Debug {
connection_start_surbs: value.connection_start_surbs,
per_request_surbs: value.per_request_surbs,
}
}
}
impl Default for Socks5DebugV1_1_20_2 {
fn default() -> Self {
Socks5DebugV1_1_20_2 {
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
}
}
}
+39 -36
View File
@@ -1,7 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::config::{Config, Socks5}; use crate::config::Config;
use crate::error::Socks5ClientCoreError; use crate::error::Socks5ClientCoreError;
use crate::socks::{ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User}, authentication::{AuthenticationMethods, Authenticator, User},
@@ -10,13 +10,16 @@ use crate::socks::{
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config;
use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore;
use nym_client_core::client::base_client::storage::MixnetClientStorage; use nym_client_core::client::base_client::storage::MixnetClientStorage;
use nym_client_core::client::base_client::{ use nym_client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
}; };
use nym_client_core::client::key_manager::persistence::KeyStore; use nym_client_core::client::key_manager::persistence::KeyStore;
use nym_client_core::client::replies::reply_storage::ReplyStorageBackend; use nym_client_core::client::replies::reply_storage::ReplyStorageBackend;
use nym_client_core::config::DebugConfig; use nym_client_core::config::DebugConfig;
use nym_client_core::init::GatewaySetup;
use nym_credential_storage::storage::Storage as CredentialStorage; use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType; use nym_sphinx::params::PacketType;
@@ -51,6 +54,8 @@ pub struct NymClient<S> {
config: Config, config: Config,
storage: S, storage: S,
setup_method: GatewaySetup,
} }
impl<S> NymClient<S> impl<S> NymClient<S>
@@ -59,16 +64,26 @@ where
S::ReplyStore: Send + Sync, S::ReplyStore: Send + Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send, <S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync, <S::CredentialStore as CredentialStorage>::StorageError: Send + Sync,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::KeyStore as KeyStore>::StorageError: Send + Sync, <S::KeyStore as KeyStore>::StorageError: Send + Sync,
{ {
pub fn new(config: Config, storage: S) -> Self { pub fn new(config: Config, storage: S) -> Self {
NymClient { config, storage } NymClient {
config,
storage,
setup_method: GatewaySetup::MustLoad,
}
}
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn start_socks5_listener( pub fn start_socks5_listener(
socks5_config: &Socks5, socks5_config: &config::Socks5,
debug_config: DebugConfig, base_debug: DebugConfig,
client_input: ClientInput, client_input: ClientInput,
client_output: ClientOutput, client_output: ClientOutput,
client_status: ClientState, client_status: ClientState,
@@ -94,25 +109,24 @@ where
.. ..
} = client_status; } = client_status;
let packet_size = debug_config let packet_size = base_debug
.traffic .traffic
.secondary_packet_size .secondary_packet_size
.unwrap_or(debug_config.traffic.primary_packet_size); .unwrap_or(base_debug.traffic.primary_packet_size);
let authenticator = Authenticator::new(auth_methods, allowed_users); let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = NymSocksServer::new( let mut sphinx_socks = NymSocksServer::new(
socks5_config.get_listening_port(), socks5_config.listening_port,
authenticator, authenticator,
socks5_config.get_provider_mix_address(), socks5_config.get_provider_mix_address(),
self_address, self_address,
shared_lane_queue_lengths, shared_lane_queue_lengths,
socks::client::Config::new( socks::client::Config::new(
packet_size, packet_size,
socks5_config.get_provider_interface_version(), socks5_config.provider_interface_version,
socks5_config.get_socks5_protocol_version(), socks5_config.socks5_protocol_version,
socks5_config.get_send_anonymously(), socks5_config.send_anonymously,
socks5_config.get_connection_start_surbs(), socks5_config.socks5_debug,
socks5_config.get_per_request_surbs(),
), ),
shutdown.clone(), shutdown.clone(),
packet_type, packet_type,
@@ -188,46 +202,35 @@ where
} }
pub async fn start(self) -> Result<StartedSocks5Client, Socks5ClientCoreError> { pub async fn start(self) -> Result<StartedSocks5Client, Socks5ClientCoreError> {
let (key_store, reply_storage_backend, credential_store) = self.storage.into_split(); // don't create dkg client for the bandwidth controller if credentials are disabled
let dkg_query_client = if self.config.base.client.disabled_credentials_mode {
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None None
} else { } else {
Some(non_wasm_helpers::create_bandwidth_controller( Some(default_query_dkg_client_from_config(&self.config.base))
self.config.get_base(),
credential_store,
))
}; };
let base_builder = BaseClientBuilder::<_, S>::new_from_base_config( let base_builder =
self.config.get_base(), BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
key_store, .with_gateway_setup(self.setup_method);
bandwidth_controller,
reply_storage_backend,
);
let packet_type = self.config.get_base().get_packet_type(); let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base(packet_type).await?; let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address; let self_address = started_client.address;
let client_input = started_client.client_input.register_producer(); let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer(); let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state; let client_state = started_client.client_state;
info!( info!("Running with {packet_type} packets",);
"Running with {:?} packets",
self.config.get_base().get_packet_type()
);
Self::start_socks5_listener( Self::start_socks5_listener(
self.config.get_socks5(), &self.config.socks5,
*self.config.get_debug_settings(), self.config.base.debug,
client_input, client_input,
client_output, client_output,
client_state, client_state,
self_address, self_address,
started_client.task_manager.subscribe(), started_client.task_manager.subscribe(),
self.config.get_base().get_packet_type(), packet_type,
); );
info!("Client startup finished!"); info!("Client startup finished!");
+8 -12
View File
@@ -4,6 +4,7 @@ use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest}; use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError}; use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError};
use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION}; use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION};
use crate::config;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::task::{Context, Poll}; use futures::task::{Context, Poll};
use log::*; use log::*;
@@ -147,16 +148,15 @@ impl Config {
provider_interface_version: ProviderInterfaceVersion, provider_interface_version: ProviderInterfaceVersion,
socks5_protocol_version: Socks5ProtocolVersion, socks5_protocol_version: Socks5ProtocolVersion,
use_surbs_for_responses: bool, use_surbs_for_responses: bool,
connection_start_surbs: u32, debug_config: config::Socks5Debug,
per_request_surbs: u32,
) -> Self { ) -> Self {
Self { Self {
biggest_packet_size, biggest_packet_size,
provider_interface_version, provider_interface_version,
socks5_protocol_version, socks5_protocol_version,
use_surbs_for_responses, use_surbs_for_responses,
connection_start_surbs, connection_start_surbs: debug_config.connection_start_surbs,
per_request_surbs, per_request_surbs: debug_config.per_request_surbs,
} }
} }
@@ -428,18 +428,14 @@ impl SocksClient {
Some(self.lane_queue_lengths.clone()), Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(), self.shutdown_listener.clone(),
) )
.run(move |conn_id, read_data, socket_closed| { .run(move |socket_data| {
let provider_request = Socks5Request::new_send( let lane = TransmissionLane::ConnectionId(socket_data.header.connection_id);
request_version.provider_protocol, let provider_request =
conn_id, Socks5Request::new_send(request_version.provider_protocol, socket_data);
read_data,
socket_closed,
);
let provider_message = Socks5ProviderRequest::new_provider_data( let provider_message = Socks5ProviderRequest::new_provider_data(
request_version.provider_interface, request_version.provider_interface,
provider_request, provider_request,
); );
let lane = TransmissionLane::ConnectionId(conn_id);
if anonymous { if anonymous {
InputMessage::new_anonymous( InputMessage::new_anonymous(
recipient, recipient,
@@ -1,19 +1,20 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Socks5ClientCoreError;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_client_core::client::received_buffer::ReconstructedMessagesReceiver; use nym_client_core::client::received_buffer::ReconstructedMessagesReceiver;
use nym_client_core::client::received_buffer::{ use nym_client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReceivedBufferMessage, ReceivedBufferRequestSender,
}; };
use nym_service_providers_common::interface::{ControlResponse, ResponseContent}; use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
use nym_socks5_proxy_helpers::connection_controller::ControllerSender; use nym_socks5_proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent}; use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use nym_sphinx::receiver::ReconstructedMessage; use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskClient; use nym_task::TaskClient;
use crate::error::Socks5ClientCoreError;
pub(crate) struct MixnetResponseListener { pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
mix_response_receiver: ReconstructedMessagesReceiver, mix_response_receiver: ReconstructedMessagesReceiver,
@@ -79,12 +80,20 @@ impl MixnetResponseListener {
); );
Err(err_response.into()) Err(err_response.into())
} }
Socks5ResponseContent::NetworkData(response) => { Socks5ResponseContent::NetworkData { content } => {
self.controller_sender self.controller_sender
.unbounded_send(response.into()) .unbounded_send(ControllerCommand::new_send(content))
.unwrap(); .unwrap();
Ok(()) Ok(())
} }
Socks5ResponseContent::Query(response) => {
error!("received a query response which we don't know how to handle yet!");
error!("got: {:?}", response);
// I guess we'd need another channel here to forward those to where they need to go
Ok(())
}
} }
} }
+172 -142
View File
@@ -1,6 +1,21 @@
use crate::message::OrderedMessage; // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use log::*; use log::*;
use std::collections::HashMap; use std::collections::BTreeMap;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum OrderedMessageError {
#[error("received message with sequence number {received}, which is way higher than our current {current}")]
MessageSequenceTooLarge { current: u64, received: u64 },
#[error("received message with sequence number {received}, while we're already at {current}!")]
MessageAlreadyReconstructed { current: u64, received: u64 },
#[error("attempted to overwrite message at sequence {received}")]
AttemptedToOverwriteSequence { received: u64 },
}
/// Stores messages and emits them in order. /// Stores messages and emits them in order.
/// ///
@@ -9,36 +24,58 @@ use std::collections::HashMap;
/// to fill up with the full sequence. /// to fill up with the full sequence.
#[derive(Debug)] #[derive(Debug)]
pub struct OrderedMessageBuffer { pub struct OrderedMessageBuffer {
next_index: u64, next_sequence: u64,
messages: HashMap<u64, OrderedMessage>, messages: BTreeMap<u64, Vec<u8>>,
} }
/// Data returned from `OrderedMessageBuffer` on a successful read of gapless ordered data. /// Data returned from `OrderedMessageBuffer` on a successful read of gapless ordered data.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ReadContiguousData { pub struct ReadContiguousData {
pub data: Vec<u8>, pub data: Vec<u8>,
pub last_index: u64, pub last_sequence: u64,
} }
const MAX_REASONABLE_OFFSET: u64 = 1000;
impl OrderedMessageBuffer { impl OrderedMessageBuffer {
pub fn new() -> OrderedMessageBuffer { pub fn new() -> OrderedMessageBuffer {
OrderedMessageBuffer { OrderedMessageBuffer {
next_index: 0, next_sequence: 0,
messages: HashMap::new(), messages: BTreeMap::new(),
} }
} }
/// Writes a message to the buffer. messages are sort on insertion, so /// Writes a message to the buffer. messages are sort on insertion, so
/// that later on multiple reads for incomplete sequences don't result in /// that later on multiple reads for incomplete sequences don't result in
/// useless sort work. /// useless sort work.
pub fn write(&mut self, message: OrderedMessage) { pub fn write(&mut self, sequence: u64, data: Vec<u8>) -> Result<(), OrderedMessageError> {
// reject messages that have clearly malformed sequence
if sequence > self.next_sequence + MAX_REASONABLE_OFFSET {
return Err(OrderedMessageError::MessageSequenceTooLarge {
current: self.next_sequence,
received: sequence,
});
}
if self.messages.contains_key(&sequence) {
return Err(OrderedMessageError::AttemptedToOverwriteSequence { received: sequence });
}
if sequence < self.next_sequence {
return Err(OrderedMessageError::MessageAlreadyReconstructed {
current: self.next_sequence,
received: sequence,
});
}
trace!( trace!(
"Writing message index: {} length {:?} to OrderedMessageBuffer.", "Writing message index: {} length {} to OrderedMessageBuffer.",
message.index, sequence,
message.data.len() data.len()
); );
self.messages.insert(message.index, message); self.messages.insert(sequence, data);
Ok(())
} }
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless /// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
@@ -49,33 +86,31 @@ impl OrderedMessageBuffer {
/// a read will return the bytes of messages 0, 1, 2. Subsequent reads will /// a read will return the bytes of messages 0, 1, 2. Subsequent reads will
/// return `None` until message 3 comes in, at which point 3, 4, and any /// return `None` until message 3 comes in, at which point 3, 4, and any
/// further contiguous messages which have arrived will be returned. /// further contiguous messages which have arrived will be returned.
#[must_use]
pub fn read(&mut self) -> Option<ReadContiguousData> { pub fn read(&mut self) -> Option<ReadContiguousData> {
if !self.messages.contains_key(&self.next_index) { if !self.messages.contains_key(&self.next_sequence) {
return None; return None;
} }
let mut contiguous_messages = Vec::new(); let mut contiguous_messages = Vec::new();
let mut index = self.next_index; let mut seq = self.next_sequence;
while let Some(ordered_message) = self.messages.remove(&index) { while let Some(mut data) = self.messages.remove(&seq) {
contiguous_messages.push(ordered_message); contiguous_messages.append(&mut data);
index += 1; seq += 1;
} }
let high_water = index; let high_water = seq;
self.next_index = high_water; self.next_sequence = high_water;
trace!("Next high water mark is: {}", high_water); trace!("Next high water mark is: {high_water}");
// dig out the bytes from inside the struct trace!(
let data: Vec<u8> = contiguous_messages "Returning {} bytes from ordered message buffer",
.into_iter() contiguous_messages.len()
.flat_map(|message| message.data) );
.collect();
trace!("Returning {} bytes from ordered message buffer", data.len());
Some(ReadContiguousData { Some(ReadContiguousData {
data, data: contiguous_messages,
last_index: index, last_sequence: seq,
}) })
} }
} }
@@ -90,6 +125,64 @@ impl Default for OrderedMessageBuffer {
mod test_chunking_and_reassembling { mod test_chunking_and_reassembling {
use super::*; use super::*;
#[test]
fn trying_to_write_unreasonable_high_sequence() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
buffer.write(0, first_message).unwrap();
buffer.write(1, second_message).unwrap();
assert_eq!(
Err(OrderedMessageError::MessageSequenceTooLarge {
current: 0,
received: 12345678
}),
buffer.write(12345678, b"foomp".to_vec())
)
}
#[test]
fn trying_to_overwrite_sequence() {
let mut buffer = OrderedMessageBuffer::new();
let message = vec![1, 2, 3, 4];
buffer.write(0, message.clone()).unwrap();
buffer.write(1, message.clone()).unwrap();
buffer.write(2, message.clone()).unwrap();
buffer.write(3, message.clone()).unwrap();
for seq in 0..=3 {
assert_eq!(
Err(OrderedMessageError::AttemptedToOverwriteSequence { received: seq }),
buffer.write(seq, message.clone())
)
}
}
#[test]
fn writing_past_data() {
let mut buffer = OrderedMessageBuffer::new();
let message = vec![1, 2, 3, 4];
buffer.write(0, message.clone()).unwrap();
buffer.write(1, message.clone()).unwrap();
buffer.write(2, message.clone()).unwrap();
buffer.write(3, message.clone()).unwrap();
let _ = buffer.read().unwrap();
for seq in 0..=3 {
assert_eq!(
Err(OrderedMessageError::MessageAlreadyReconstructed {
current: 4,
received: seq
}),
buffer.write(seq, message.clone())
)
}
}
#[cfg(test)] #[cfg(test)]
mod reading_from_and_writing_to_the_buffer { mod reading_from_and_writing_to_the_buffer {
use super::*; use super::*;
@@ -102,20 +195,14 @@ mod test_chunking_and_reassembling {
fn read_returns_ordered_bytes_and_resets_buffer() { fn read_returns_ordered_bytes_and_resets_buffer() {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage { let first_message = vec![1, 2, 3, 4];
data: vec![1, 2, 3, 4], let second_message = vec![5, 6, 7, 8];
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(first_message); buffer.write(0, first_message).unwrap();
let first_read = buffer.read().unwrap().data; let first_read = buffer.read().unwrap().data;
assert_eq!(vec![1, 2, 3, 4], first_read); assert_eq!(vec![1, 2, 3, 4], first_read);
buffer.write(second_message); buffer.write(1, second_message).unwrap();
let second_read = buffer.read().unwrap().data; let second_read = buffer.read().unwrap().data;
assert_eq!(vec![5, 6, 7, 8], second_read); assert_eq!(vec![5, 6, 7, 8], second_read);
@@ -126,17 +213,11 @@ mod test_chunking_and_reassembling {
fn test_multiple_adds_stacks_up_bytes_in_the_buffer() { fn test_multiple_adds_stacks_up_bytes_in_the_buffer() {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage { let first_message = vec![1, 2, 3, 4];
data: vec![1, 2, 3, 4], let second_message = vec![5, 6, 7, 8];
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(first_message); buffer.write(0, first_message).unwrap();
buffer.write(second_message); buffer.write(1, second_message).unwrap();
let second_read = buffer.read(); let second_read = buffer.read();
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], second_read.unwrap().data); assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], second_read.unwrap().data);
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
@@ -146,17 +227,11 @@ mod test_chunking_and_reassembling {
fn out_of_order_adds_results_in_ordered_byte_vector() { fn out_of_order_adds_results_in_ordered_byte_vector() {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage { let first_message = vec![1, 2, 3, 4];
data: vec![1, 2, 3, 4], let second_message = vec![5, 6, 7, 8];
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
buffer.write(second_message); buffer.write(1, second_message).unwrap();
buffer.write(first_message); buffer.write(0, first_message).unwrap();
let read = buffer.read().unwrap().data; let read = buffer.read().unwrap().data;
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], read); assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], read);
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
@@ -170,23 +245,13 @@ mod test_chunking_and_reassembling {
fn setup() -> OrderedMessageBuffer { fn setup() -> OrderedMessageBuffer {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage { let zero_message = vec![0, 0, 0, 0];
data: vec![0, 0, 0, 0], let one_message = vec![1, 1, 1, 1];
index: 0, let three_message = vec![3, 3, 3, 3];
};
let one_message = OrderedMessage {
data: vec![1, 1, 1, 1],
index: 1,
};
let three_message = OrderedMessage { buffer.write(0, zero_message).unwrap();
data: vec![3, 3, 3, 3], buffer.write(1, one_message).unwrap();
index: 3, buffer.write(3, three_message).unwrap();
};
buffer.write(zero_message);
buffer.write(one_message);
buffer.write(three_message);
buffer buffer
} }
#[test] #[test]
@@ -199,43 +264,31 @@ mod test_chunking_and_reassembling {
assert_eq!(None, buffer.read()); assert_eq!(None, buffer.read());
// let's add another message, leaving a gap in place at index 2 // let's add another message, leaving a gap in place at index 2
let five_message = OrderedMessage { let five_message = vec![5, 5, 5, 5];
data: vec![5, 5, 5, 5], buffer.write(5, five_message).unwrap();
index: 5,
};
buffer.write(five_message);
assert_eq!(None, buffer.read()); assert_eq!(None, buffer.read());
} }
#[test] #[test]
fn filling_the_gap_allows_us_to_get_everything() { fn filling_the_gap_allows_us_to_get_everything() {
let mut buffer = setup(); let mut buffer = setup();
buffer.read(); // that burns the first two. We still have a gap before the 3s. let _ = buffer.read(); // that burns the first two. We still have a gap before the 3s.
let two_message = OrderedMessage { let two_message = vec![2, 2, 2, 2];
data: vec![2, 2, 2, 2], buffer.write(2, two_message).unwrap();
index: 2,
};
buffer.write(two_message);
let more_ordered_bytes = buffer.read().unwrap().data; let more_ordered_bytes = buffer.read().unwrap().data;
assert_eq!([2, 2, 2, 2, 3, 3, 3, 3].to_vec(), more_ordered_bytes); assert_eq!([2, 2, 2, 2, 3, 3, 3, 3].to_vec(), more_ordered_bytes);
// let's add another message // let's add another message
let five_message = OrderedMessage { let five_message = vec![5, 5, 5, 5];
data: vec![5, 5, 5, 5], buffer.write(5, five_message).unwrap();
index: 5,
};
buffer.write(five_message);
assert_eq!(None, buffer.read()); assert_eq!(None, buffer.read());
// let's fill in the gap of 4s now and read again // let's fill in the gap of 4s now and read again
let four_message = OrderedMessage { let four_message = vec![4, 4, 4, 4];
data: vec![4, 4, 4, 4], buffer.write(4, four_message).unwrap();
index: 4,
};
buffer.write(four_message);
assert_eq!( assert_eq!(
[4, 4, 4, 4, 5, 5, 5, 5].to_vec(), [4, 4, 4, 4, 5, 5, 5, 5].to_vec(),
@@ -249,70 +302,47 @@ mod test_chunking_and_reassembling {
#[test] #[test]
fn filling_the_gap_allows_us_to_get_everything_when_last_element_is_empty() { fn filling_the_gap_allows_us_to_get_everything_when_last_element_is_empty() {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage { let zero_message = vec![0, 0, 0, 0];
data: vec![0, 0, 0, 0], let one_message = vec![2, 2, 2, 2];
index: 0, let two_message = vec![];
};
let one_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 1,
};
let two_message = OrderedMessage {
data: vec![],
index: 2,
};
buffer.write(zero_message); buffer.write(0, zero_message).unwrap();
assert!(buffer.read().is_some()); // burn the buffer assert!(buffer.read().is_some()); // burn the buffer
buffer.write(two_message); buffer.write(2, two_message).unwrap();
buffer.write(one_message); buffer.write(1, one_message).unwrap();
assert!(buffer.read().is_some()); assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 3); assert_eq!(buffer.next_sequence, 3);
} }
#[test] #[test]
fn works_with_gaps_bigger_than_one() { fn works_with_gaps_bigger_than_one() {
let mut buffer = OrderedMessageBuffer::new(); let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage { let zero_message = vec![0, 0, 0, 0];
data: vec![0, 0, 0, 0], let one_message = vec![2, 2, 2, 2];
index: 0, let two_message = vec![2, 2, 2, 2];
}; let three_message = vec![2, 2, 2, 2];
let one_message = OrderedMessage { let four_message = vec![2, 2, 2, 2];
data: vec![2, 2, 2, 2],
index: 1, buffer.write(0, zero_message).unwrap();
};
let two_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 2,
};
let three_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 3,
};
let four_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 4,
};
buffer.write(zero_message);
assert!(buffer.read().is_some()); assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 1); assert_eq!(buffer.next_sequence, 1);
buffer.write(four_message); buffer.write(4, four_message).unwrap();
assert!(buffer.read().is_none()); assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1); assert_eq!(buffer.next_sequence, 1);
buffer.write(three_message); buffer.write(3, three_message).unwrap();
assert!(buffer.read().is_none()); assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1); assert_eq!(buffer.next_sequence, 1);
buffer.write(two_message); buffer.write(2, two_message).unwrap();
assert!(buffer.read().is_none()); assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1); assert_eq!(buffer.next_sequence, 1);
buffer.write(one_message); buffer.write(1, one_message).unwrap();
assert!(buffer.read().is_some()); assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 5) assert_eq!(buffer.next_sequence, 5)
} }
} }
} }
+1 -6
View File
@@ -1,8 +1,3 @@
mod buffer; mod buffer;
mod message;
mod sender;
pub use buffer::{OrderedMessageBuffer, ReadContiguousData}; pub use buffer::{OrderedMessageBuffer, OrderedMessageError, ReadContiguousData};
pub use message::MessageError;
pub use message::OrderedMessage;
pub use sender::OrderedMessageSender;
-143
View File
@@ -1,143 +0,0 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::cmp::Ordering;
use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum MessageError {
#[error("the received message was empty")]
NoData,
#[error("could not extract message index. Received {received} bytes, but expected {expected}")]
IndexTooShort { received: usize, expected: usize },
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OrderedMessage {
pub data: Vec<u8>,
pub index: u64,
}
impl OrderedMessage {
/// Serializes an `OrderedMessage` into bytes.
/// The output format is:
/// | 8 bytes index | data... |
pub fn into_bytes(self) -> Vec<u8> {
self.index
.to_be_bytes()
.iter()
.cloned()
.chain(self.data.into_iter())
.collect()
}
/// Attempts to deserialize an `OrderedMessage` from bytes.
pub fn try_from_bytes(data: Vec<u8>) -> Result<OrderedMessage, MessageError> {
if data.is_empty() {
return Err(MessageError::NoData);
}
if data.len() < 8 {
return Err(MessageError::IndexTooShort {
received: data.len(),
expected: 8,
});
}
let index = u64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Ok(OrderedMessage {
data: data[8..].to_vec(),
index,
})
}
}
/// Order messages by their index only, ignoring their data
impl PartialOrd for OrderedMessage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some((self.index).cmp(&(other.index)))
}
}
#[cfg(test)]
mod ordered_message_to_bytes {
use super::*;
#[test]
fn works() {
let message = OrderedMessage {
data: vec![123],
index: 1,
};
let bytes = message.into_bytes();
let expected = vec![0, 0, 0, 0, 0, 0, 0, 1, 123];
assert_eq!(expected, bytes);
}
}
#[cfg(test)]
mod ordered_message_from_bytes {
use super::*;
#[test]
fn fails_when_there_is_no_data() {
let result = OrderedMessage::try_from_bytes(Vec::new());
assert_eq!(Err(MessageError::NoData), result);
}
#[test]
fn fails_when_data_is_too_short() {
let result = OrderedMessage::try_from_bytes(vec![1, 2, 3]);
assert_eq!(
Err(MessageError::IndexTooShort {
received: 3,
expected: 8
}),
result
);
}
#[test]
fn works_when_there_is_enough_to_make_a_sequence_number_but_no_message_data() {
let expected = OrderedMessage {
data: Vec::new(),
index: 1,
};
let result = OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1]).unwrap();
assert_eq!(expected, result);
}
#[test]
fn works_when_there_is_seq_number_and_data() {
let expected = OrderedMessage {
data: vec![255, 255, 255],
index: 1,
};
let result =
OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255]).unwrap();
assert_eq!(expected, result);
}
}
#[test]
fn empty_message_does_not_affect_ordering() {
let mut msg1 = OrderedMessage {
data: vec![255, 255, 255],
index: 1,
};
let mut msg2 = OrderedMessage {
data: vec![],
index: 2,
};
assert!(msg1 < msg2);
msg1.index = 2;
msg2.index = 1;
assert!(msg1 > msg2);
}
@@ -1,57 +0,0 @@
use crate::message::OrderedMessage;
/// Assigns sequence numbers to outbound byte vectors. These messages can then
/// be reassembled into an ordered sequence by the `OrderedMessageSender`.
#[derive(Debug)]
pub struct OrderedMessageSender {
next_index: u64,
}
impl OrderedMessageSender {
pub fn new() -> OrderedMessageSender {
OrderedMessageSender { next_index: 0 }
}
/// Turns raw bytes into an OrderedMessage containing the original bytes
/// and a sequence number;
pub fn wrap_message(&mut self, input: Vec<u8>) -> OrderedMessage {
let message = OrderedMessage {
data: input.to_vec(),
index: self.next_index,
};
self.next_index += 1;
message
}
}
impl Default for OrderedMessageSender {
fn default() -> Self {
OrderedMessageSender::new()
}
}
#[cfg(test)]
mod ordered_message_sender {
use super::*;
mod when_input_bytes_are_empty {}
#[cfg(test)]
mod sequence_index_numbers {
use super::*;
#[test]
fn increase_as_messages_are_sent() {
let mut sender = OrderedMessageSender::new();
let first_bytes = vec![1, 2, 3, 4];
let second_bytes = vec![5, 6, 7, 8];
let first_message = sender.wrap_message(first_bytes);
assert_eq!(first_message.index, 0);
let second_message = sender.wrap_message(second_bytes);
assert_eq!(second_message.index, 1);
}
}
}
@@ -1,11 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; use nym_ordered_buffer::{OrderedMessageBuffer, ReadContiguousData};
use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest}; use nym_socks5_requests::{ConnectionId, SocketData};
use nym_task::connections::{ConnectionCommand, ConnectionCommandSender}; use nym_task::connections::{ConnectionCommand, ConnectionCommandSender};
use nym_task::TaskClient; use nym_task::TaskClient;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -40,29 +40,13 @@ pub enum ControllerCommand {
connection_id: ConnectionId, connection_id: ConnectionId,
}, },
Send { Send {
connection_id: ConnectionId, data: SocketData,
data: Vec<u8>,
is_closed: bool,
}, },
} }
impl From<NetworkData> for ControllerCommand { impl ControllerCommand {
fn from(value: NetworkData) -> Self { pub fn new_send(data: SocketData) -> Self {
ControllerCommand::Send { ControllerCommand::Send { data }
connection_id: value.connection_id,
data: value.data,
is_closed: value.is_closed,
}
}
}
impl From<SendRequest> for ControllerCommand {
fn from(value: SendRequest) -> Self {
ControllerCommand::Send {
connection_id: value.conn_id,
data: value.data,
is_closed: value.local_closed,
}
} }
} }
@@ -74,18 +58,13 @@ struct ActiveConnection {
} }
impl ActiveConnection { impl ActiveConnection {
fn write_to_buf(&mut self, payload: Vec<u8>, is_closed: bool) { fn write_to_buf(&mut self, seq: u64, payload: Vec<u8>, is_closed: bool) {
let ordered_message = match OrderedMessage::try_from_bytes(payload) {
Ok(msg) => msg,
Err(err) => {
error!("Malformed ordered message - {err}");
return;
}
};
if is_closed { if is_closed {
self.closed_at_index = Some(ordered_message.index); self.closed_at_index = Some(seq);
}
if let Err(err) = self.ordered_buffer.write(seq, payload) {
error!("failed to write to the buffer: {err}")
} }
self.ordered_buffer.write(ordered_message);
} }
fn read_from_buf(&mut self) -> Option<ReadContiguousData> { fn read_from_buf(&mut self) -> Option<ReadContiguousData> {
@@ -117,7 +96,7 @@ pub struct Controller {
// buffer for messages received before connection was established due to mixnet being able to // buffer for messages received before connection was established due to mixnet being able to
// un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here // un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here
pending_messages: HashMap<ConnectionId, Vec<(Vec<u8>, bool)>>, pending_messages: HashMap<ConnectionId, Vec<SocketData>>,
shutdown: TaskClient, shutdown: TaskClient,
} }
@@ -154,8 +133,8 @@ impl Controller {
// check if there were any pending messages // check if there were any pending messages
if let Some(pending) = self.pending_messages.remove(&conn_id) { if let Some(pending) = self.pending_messages.remove(&conn_id) {
debug!("There were some pending messages for {}", conn_id); debug!("There were some pending messages for {}", conn_id);
for (payload, is_closed) in pending { for data in pending {
self.send_to_connection(conn_id, payload, is_closed) self.send_to_connection(data)
} }
} }
} }
@@ -184,20 +163,25 @@ impl Controller {
} }
} }
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) { fn send_to_connection(&mut self, message: SocketData) {
if let Some(active_connection) = self.active_connections.get_mut(&conn_id) { let hdr = message.header;
if !payload.is_empty() { if let Some(active_connection) = self.active_connections.get_mut(&hdr.connection_id) {
active_connection.write_to_buf(payload, is_closed); // always write to the buffer even if payload is empty (because it could have been the keep-alive message)
} else if !is_closed { active_connection.write_to_buf(hdr.seq, message.data, hdr.local_socket_closed);
error!("Tried to write an empty message to a not-closing connection. Please let us know if you see this message");
}
if let Some(payload) = active_connection.read_from_buf() { if let Some(payload) = active_connection.read_from_buf() {
if let Some(closed_at_index) = active_connection.closed_at_index { if let Some(closed_at_index) = active_connection.closed_at_index {
if payload.last_index > closed_at_index { if payload.last_sequence > closed_at_index {
active_connection.is_closed = true; active_connection.is_closed = true;
} }
} }
// however, don't send empty payload to the actual connection if it's not a close message
// TODO: or should we?
if payload.data.is_empty() && !active_connection.is_closed {
return;
}
if let Err(err) = active_connection if let Err(err) = active_connection
.connection_sender .connection_sender
.as_mut() .as_mut()
@@ -207,34 +191,26 @@ impl Controller {
socket_closed: active_connection.is_closed, socket_closed: active_connection.is_closed,
}) })
{ {
error!("WTF IS THIS: {err}"); error!("failed to send on the active connection channel: {err}");
} }
// TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!!
// TODO:
// TODO: surprisingly it only happened on socks client, never on nSP
// TODO:
// TODO:
// TODO:
// TODO:
} }
} else if !self.recently_closed.contains(&conn_id) { } else if !self.recently_closed.contains(&hdr.connection_id) {
debug!("Received a 'Send' before 'Connect' - going to buffer the data"); debug!("Received a 'Send' before 'Connect' - going to buffer the data");
let pending = self let pending = self
.pending_messages .pending_messages
.entry(conn_id) .entry(hdr.connection_id)
.or_insert_with(Vec::new); .or_insert_with(Vec::new);
pending.push((payload, is_closed)); pending.push(message);
} else if !is_closed { } else if !hdr.local_socket_closed {
error!( error!(
"Tried to write to closed connection {} ({} bytes were 'lost)", "Tried to write to closed connection {} ({} bytes were 'lost)",
conn_id, hdr.connection_id,
payload.len() message.data.len()
); );
} else { } else {
debug!( debug!(
"Tried to write to closed connection {}, but remote is already closed", "Tried to write to closed connection {}, but remote is already closed",
conn_id hdr.connection_id
) )
} }
} }
@@ -243,8 +219,8 @@ impl Controller {
loop { loop {
tokio::select! { tokio::select! {
command = self.receiver.next() => match command { command = self.receiver.next() => match command {
Some(ControllerCommand::Send{connection_id, data, is_closed}) => { Some(ControllerCommand::Send{data}) => {
self.send_to_connection(connection_id, data, is_closed) self.send_to_connection(data)
} }
Some(ControllerCommand::Insert{connection_id, connection_sender}) => { Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
self.insert_connection(connection_id, connection_sender) self.insert_connection(connection_id, connection_sender)
+1
View File
@@ -3,4 +3,5 @@
pub mod available_reader; pub mod available_reader;
pub mod connection_controller; pub mod connection_controller;
pub mod ordered_sender;
pub mod proxy_runner; pub mod proxy_runner;
@@ -0,0 +1,116 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::proxy_runner::MixProxySender;
use bytes::Bytes;
use log::{debug, error};
use nym_socks5_requests::{ConnectionId, SocketData};
use std::io;
pub(crate) struct OrderedMessageSender<F, S> {
connection_id: ConnectionId,
// addresses are provided for better logging
local_destination_address: String,
remote_source_address: String,
mixnet_sender: MixProxySender<S>,
next_message_seq: u64,
mix_message_adapter: F,
}
impl<F, S> OrderedMessageSender<F, S>
where
F: Fn(SocketData) -> S,
{
pub(crate) fn new(
local_destination_address: String,
remote_source_address: String,
connection_id: ConnectionId,
mixnet_sender: MixProxySender<S>,
mix_message_adapter: F,
) -> Self {
OrderedMessageSender {
local_destination_address,
remote_source_address,
connection_id,
mixnet_sender,
next_message_seq: 0,
mix_message_adapter,
}
}
fn sequence(&mut self) -> u64 {
let next = self.next_message_seq;
self.next_message_seq += 1;
next
}
fn construct_message(&mut self, data: Vec<u8>, local_socket_closed: bool) -> S {
let data = SocketData::new(
self.sequence(),
self.connection_id,
local_socket_closed,
data,
);
(self.mix_message_adapter)(data)
}
async fn send_message(&self, message: S) {
if self.mixnet_sender.send(message).await.is_err() {
panic!("BatchRealMessageReceiver has stopped receiving!")
}
}
pub(crate) async fn send_empty_close(&mut self) {
let message = self.construct_message(Vec::new(), true);
self.send_message(message).await
}
pub(crate) async fn send_empty_keepalive(&mut self) {
log::trace!("Sending keepalive for connection: {}", self.connection_id);
let message = self.construct_message(Vec::new(), false);
self.send_message(message).await
}
pub(crate) fn process_data(&self, read_data: Option<io::Result<Bytes>>) -> ProcessedData {
let (read_data, is_finished) = match read_data {
Some(data) => match data {
Ok(data) => (data, false),
Err(err) => {
error!(target: &*format!("({}) socks5 inbound", self.connection_id), "failed to read request from the socket - {err}");
(Default::default(), true)
}
},
None => (Default::default(), true),
};
ProcessedData {
data: read_data,
is_done: is_finished,
}
}
fn log_sent_message(&self, data: &ProcessedData) {
debug!(
target: &*format!("({}) socks5 inbound", self.connection_id),
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
data.data.len(),
self.local_destination_address,
self.remote_source_address,
data.is_done
);
}
/// Send data read from local socket into the mixnet
pub(crate) async fn send_data(&mut self, data: ProcessedData) {
self.log_sent_message(&data);
let message = self.construct_message(data.data.into(), data.is_done);
self.send_message(message).await;
}
}
// helper wrapper to keep track of field meanings
pub(crate) struct ProcessedData {
data: Bytes,
pub(crate) is_done: bool,
}
@@ -1,106 +1,22 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use super::MixProxySender;
use super::SHUTDOWN_TIMEOUT; use super::SHUTDOWN_TIMEOUT;
use crate::available_reader::AvailableReader; use crate::available_reader::AvailableReader;
use crate::ordered_sender::OrderedMessageSender;
use crate::proxy_runner::KEEPALIVE_INTERVAL; use crate::proxy_runner::KEEPALIVE_INTERVAL;
use bytes::Bytes;
use futures::FutureExt; use futures::FutureExt;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nym_ordered_buffer::OrderedMessageSender; use nym_socks5_requests::{ConnectionId, SocketData};
use nym_socks5_requests::ConnectionId;
use nym_task::connections::LaneQueueLengths; use nym_task::connections::LaneQueueLengths;
use nym_task::connections::TransmissionLane; use nym_task::connections::TransmissionLane;
use nym_task::TaskClient; use nym_task::TaskClient;
use std::fmt::Debug; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{io, sync::Arc};
use tokio::select; use tokio::select;
use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep}; use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep};
async fn send_empty_close<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
mix_sender
.send(adapter_fn(connection_id, ordered_msg, true))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
async fn send_empty_keepalive<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
log::trace!("Sending keepalive for connection: {connection_id}");
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
mix_sender
.send(adapter_fn(connection_id, ordered_msg, false))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
async fn deal_with_data<F, S>(
read_data: Option<io::Result<Bytes>>,
local_destination_address: &str,
remote_source_address: &str,
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) -> bool
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
let (read_data, is_finished) = match read_data {
Some(data) => match data {
Ok(data) => (data, false),
Err(err) => {
error!(target: &*format!("({connection_id}) socks5 inbound"), "failed to read request from the socket - {err}");
(Default::default(), true)
}
},
None => (Default::default(), true),
};
debug!(
target: &*format!("({connection_id}) socks5 inbound"),
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
read_data.len(),
local_destination_address,
remote_source_address,
is_finished
);
// if we're sending through the mixnet increase the sequence number...
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
log::trace!(
"pushing data down the input sender: size: {}",
ordered_msg.len()
);
mix_sender
.send(adapter_fn(connection_id, ordered_msg, is_finished))
.await
.expect("InputMessageReceiver has stopped receiving!");
is_finished
}
async fn wait_until_lane_empty(lane_queue_lengths: &Option<LaneQueueLengths>, connection_id: u64) { async fn wait_until_lane_empty(lane_queue_lengths: &Option<LaneQueueLengths>, connection_id: u64) {
if let Some(lane_queue_lengths) = lane_queue_lengths { if let Some(lane_queue_lengths) = lane_queue_lengths {
if tokio::time::timeout( if tokio::time::timeout(
@@ -158,27 +74,21 @@ async fn wait_for_lane(
} }
} }
#[allow(clippy::too_many_arguments)]
pub(super) async fn run_inbound<F, S>( pub(super) async fn run_inbound<F, S>(
mut reader: OwnedReadHalf, mut reader: OwnedReadHalf,
local_destination_address: String, // addresses are provided for better logging mut message_sender: OrderedMessageSender<F, S>,
remote_source_address: String,
connection_id: ConnectionId, connection_id: ConnectionId,
mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize, available_plaintext_per_mix_packet: usize,
adapter_fn: F,
shutdown_notify: Arc<Notify>, shutdown_notify: Arc<Notify>,
lane_queue_lengths: Option<LaneQueueLengths>, lane_queue_lengths: Option<LaneQueueLengths>,
mut shutdown_listener: TaskClient, mut shutdown_listener: TaskClient,
) -> OwnedReadHalf ) -> OwnedReadHalf
where where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static, F: Fn(SocketData) -> S + Send + 'static,
S: Debug,
{ {
// TODO: this multiplication by 4 is completely arbitrary here // TODO: this multiplication by 4 is completely arbitrary here
let mut available_reader = let mut available_reader =
AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4)); AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4));
let mut message_sender = OrderedMessageSender::new();
// Shutdown if outbound signled to shutdown // Shutdown if outbound signled to shutdown
let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT)); let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT));
@@ -217,7 +127,7 @@ where
); );
// inform remote just in case it was closed because of lack of heartbeat. // inform remote just in case it was closed because of lack of heartbeat.
// worst case the remote will just have couple of false negatives // worst case the remote will just have couple of false negatives
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await; message_sender.send_empty_close().await;
break; break;
} }
_ = shutdown_listener.recv() => { _ = shutdown_listener.recv() => {
@@ -233,7 +143,7 @@ where
break; break;
} }
_ = keepalive_timer.tick() => { _ = keepalive_timer.tick() => {
send_empty_keepalive(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await; message_sender.send_empty_keepalive().await;
} }
// Read the next data when there is space in the lane. // Read the next data when there is space in the lane.
// The purpose of chaining the wait here is that it makes sure we can cancel the // The purpose of chaining the wait here is that it makes sure we can cancel the
@@ -241,15 +151,12 @@ where
read_data = wait_until_lane_almost_empty(&lane_queue_lengths, connection_id) read_data = wait_until_lane_almost_empty(&lane_queue_lengths, connection_id)
.then(|_| available_reader.next()), if !we_are_closed => .then(|_| available_reader.next()), if !we_are_closed =>
{ {
if deal_with_data( let processed = message_sender.process_data(read_data);
read_data, let is_done = processed.is_done;
&local_destination_address,
&remote_source_address, message_sender.send_data(processed).await;
connection_id,
&mut message_sender, if is_done {
&mix_sender,
&adapter_fn,
).await {
// After reading the last data, notify the closing_future to wait until the // After reading the last data, notify the closing_future to wait until the
// lane is clear before exiting. // lane is clear before exiting.
// We don't wait here since we want to be able to cancel the wait on close or // We don't wait here since we want to be able to cancel the wait on close or
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver; use crate::connection_controller::ConnectionReceiver;
use nym_socks5_requests::ConnectionId; use crate::ordered_sender::OrderedMessageSender;
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_task::connections::LaneQueueLengths; use nym_task::connections::LaneQueueLengths;
use nym_task::TaskClient; use nym_task::TaskClient;
use std::fmt::Debug; use std::fmt::Debug;
@@ -92,20 +93,24 @@ where
// request/response as required by entity running particular side of the proxy. // request/response as required by entity running particular side of the proxy.
pub async fn run<F>(mut self, adapter_fn: F) -> Self pub async fn run<F>(mut self, adapter_fn: F) -> Self
where where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + Sync + 'static, F: Fn(SocketData) -> S + Send + Sync + 'static,
{ {
let (read_half, write_half) = self.socket.take().unwrap().into_split(); let (read_half, write_half) = self.socket.take().unwrap().into_split();
let shutdown_notify = Arc::new(Notify::new()); let shutdown_notify = Arc::new(Notify::new());
// should run until either inbound closes or is notified from outbound // should run until either inbound closes or is notified from outbound
let inbound_future = inbound::run_inbound( let ordered_sender = OrderedMessageSender::new(
read_half,
self.local_destination_address.clone(), self.local_destination_address.clone(),
self.remote_source_address.clone(), self.remote_source_address.clone(),
self.connection_id, self.connection_id,
self.mix_sender.clone(), self.mix_sender.clone(),
self.available_plaintext_per_mix_packet,
adapter_fn, adapter_fn,
);
let inbound_future = inbound::run_inbound(
read_half,
ordered_sender,
self.connection_id,
self.available_plaintext_per_mix_packet,
Arc::clone(&shutdown_notify), Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(), self.lane_queue_lengths.clone(),
self.shutdown_listener.clone(), self.shutdown_listener.clone(),
+6 -4
View File
@@ -7,9 +7,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
thiserror = { workspace = true } bincode = "1.3.3"
log = { workspace = true }
nym-service-providers-common = { path = "../../../service-providers/common" }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
tap = { workspace = true }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } thiserror = { workspace = true }
nym-service-providers-common = { path = "../../../service-providers/common" }
+293 -3
View File
@@ -3,6 +3,7 @@
use nym_service_providers_common::interface; use nym_service_providers_common::interface;
use nym_service_providers_common::interface::ServiceProviderMessagingError; use nym_service_providers_common::interface::ServiceProviderMessagingError;
use std::mem;
use thiserror::Error; use thiserror::Error;
pub use request::*; pub use request::*;
@@ -16,6 +17,185 @@ pub mod version;
pub type Socks5ProviderRequest = interface::Request<Socks5Request>; pub type Socks5ProviderRequest = interface::Request<Socks5Request>;
pub type Socks5ProviderResponse = interface::Response<Socks5Request>; pub type Socks5ProviderResponse = interface::Response<Socks5Request>;
#[derive(Debug, Error, PartialEq, Eq)]
#[error(
"didn't receive enough data to recover socket data. got {received}, but expected at least {expected}"
)]
pub struct InsufficientSocketDataError {
received: usize,
expected: usize,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct SocketDataHeader {
pub seq: u64,
pub connection_id: ConnectionId,
pub local_socket_closed: bool,
}
impl SocketDataHeader {
const SERIALIZED_LEN: usize = mem::size_of::<ConnectionId>() + 1 + mem::size_of::<u64>();
// we need to have two serialization methods for backwards compatibility,
// since we serialized those fields differently depending on whether it was ingress vs egress...
pub fn try_from_request_bytes(
b: &[u8],
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
if b.len() != Self::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: Self::SERIALIZED_LEN,
});
}
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
let connection_id = ConnectionId::from_be_bytes(b[0..8].try_into().unwrap());
let local_socket_closed = b[8] != 0;
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
Ok(SocketDataHeader {
seq,
connection_id,
local_socket_closed,
})
}
// the serialization of the header looks as follows:
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
// CONNECTION_ID (8B) || SOCKET_CLOSED (1B) || SEQUENCE (8B)
pub fn into_request_bytes(self) -> Vec<u8> {
self.into_request_bytes_iter().collect()
}
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
self.connection_id
.to_be_bytes()
.into_iter()
.chain(std::iter::once(self.local_socket_closed as u8))
.chain(self.seq.to_be_bytes().into_iter())
}
pub fn try_from_response_bytes(
b: &[u8],
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
if b.len() != Self::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: Self::SERIALIZED_LEN,
});
}
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
let local_socket_closed = b[0] != 0;
let connection_id = ConnectionId::from_be_bytes(b[1..9].try_into().unwrap());
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
Ok(SocketDataHeader {
seq,
connection_id,
local_socket_closed,
})
}
// the serialization of the header looks as follows:
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
// SOCKET_CLOSED (1B) || CONNECTION_ID (8B) || SEQUENCE (8B)
pub fn into_response_bytes(self) -> Vec<u8> {
self.into_response_bytes_iter().collect()
}
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
std::iter::once(self.local_socket_closed as u8)
.chain(self.connection_id.to_be_bytes().into_iter())
.chain(self.seq.to_be_bytes().into_iter())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SocketData {
pub header: SocketDataHeader,
pub data: Vec<u8>,
}
impl SocketData {
pub fn new(
seq: u64,
connection_id: ConnectionId,
local_socket_closed: bool,
data: Vec<u8>,
) -> Self {
SocketData {
header: SocketDataHeader {
seq,
connection_id,
local_socket_closed,
},
data,
}
}
fn verify_deserialization_len(b: &[u8]) -> Result<(), InsufficientSocketDataError> {
if b.is_empty() {
return Err(InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN,
});
}
if b.len() < SocketDataHeader::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: SocketDataHeader::SERIALIZED_LEN,
});
}
Ok(())
}
// we need to have two serialization methods for backwards compatibility,
// since we serialized those fields differently depending on whether it was ingress vs egress...
pub fn try_from_request_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
Self::verify_deserialization_len(b)?;
let header =
SocketDataHeader::try_from_request_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
Ok(SocketData { header, data })
}
// the serialization of the socket data looks as follows:
// HEADER || DATA
pub fn into_request_bytes(self) -> Vec<u8> {
self.into_request_bytes_iter().collect()
}
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_request_bytes_iter()
.chain(self.data.into_iter())
}
pub fn try_from_response_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
Self::verify_deserialization_len(b)?;
let header =
SocketDataHeader::try_from_response_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
Ok(SocketData { header, data })
}
pub fn into_response_bytes(self) -> Vec<u8> {
self.into_response_bytes_iter().collect()
}
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_response_bytes_iter()
.chain(self.data.into_iter())
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Socks5RequestError { pub enum Socks5RequestError {
#[error("failed to deserialize received request: {source}")] #[error("failed to deserialize received request: {source}")]
@@ -32,6 +212,18 @@ pub enum Socks5RequestError {
#[error(transparent)] #[error(transparent)]
ProviderInterfaceError(#[from] ServiceProviderMessagingError), ProviderInterfaceError(#[from] ServiceProviderMessagingError),
#[error("received unsupported request protocol version: {protocol_version}")]
UnsupportedProtocolVersion {
protocol_version: <Socks5Request as interface::ServiceProviderRequest>::ProtocolVersion,
},
}
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
} }
#[cfg(test)] #[cfg(test)]
@@ -39,6 +231,103 @@ mod tests {
use super::*; use super::*;
use nym_service_providers_common::interface::RequestContent; use nym_service_providers_common::interface::RequestContent;
#[cfg(test)]
mod socket_data_serialization {
use super::*;
#[test]
fn for_requests() {
assert_eq!(
InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_request_bytes(&[]).unwrap_err()
);
assert_eq!(
InsufficientSocketDataError {
received: 10,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_request_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
);
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
let serialized = good_data.clone().into_request_bytes();
assert_eq!(
good_data,
SocketData::try_from_request_bytes(&serialized).unwrap()
);
assert_ne!(
good_data,
SocketData::try_from_response_bytes(&serialized).unwrap()
);
let raw_bytes = [
6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
];
assert_eq!(
SocketData {
header: SocketDataHeader {
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
local_socket_closed: false,
},
data: vec![255, 255, 255],
},
SocketData::try_from_request_bytes(&raw_bytes).unwrap()
)
}
#[test]
fn for_responses() {
assert_eq!(
InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_response_bytes(&[]).unwrap_err()
);
assert_eq!(
InsufficientSocketDataError {
received: 10,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_response_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
);
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
let serialized = good_data.clone().into_response_bytes();
assert_eq!(
good_data,
SocketData::try_from_response_bytes(&serialized).unwrap()
);
assert_ne!(
good_data,
SocketData::try_from_request_bytes(&serialized).unwrap()
);
let raw_bytes = [
0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
];
assert_eq!(
SocketData {
header: SocketDataHeader {
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
local_socket_closed: false,
},
data: vec![255, 255, 255],
},
SocketData::try_from_response_bytes(&raw_bytes).unwrap()
)
}
}
#[cfg(test)] #[cfg(test)]
mod interface_backwards_compatibility { mod interface_backwards_compatibility {
use super::*; use super::*;
@@ -87,9 +376,10 @@ mod tests {
match new_deserialized.content { match new_deserialized.content {
RequestContent::ProviderData(req) => match req.content { RequestContent::ProviderData(req) => match req.content {
Socks5RequestContent::Send(send_req) => { Socks5RequestContent::Send(send_req) => {
assert_eq!(send_req.conn_id, 7810961472501196273); assert_eq!(send_req.data.header.connection_id, 7810961472501196273);
assert_eq!(send_req.data.len(), 111); assert_eq!(send_req.data.header.seq, 0);
assert!(!send_req.local_closed); assert_eq!(send_req.data.data.len(), 103);
assert!(!send_req.data.header.local_socket_closed);
} }
_ => panic!("unexpected request"), _ => panic!("unexpected request"),
}, },
+160 -66
View File
@@ -1,10 +1,15 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response}; use crate::{
make_bincode_serializer, InsufficientSocketDataError, SocketData, Socks5ProtocolVersion,
Socks5RequestError, Socks5Response,
};
use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest}; use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest};
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError}; use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use tap::TapFallible;
use thiserror::Error; use thiserror::Error;
pub type ConnectionId = u64; pub type ConnectionId = u64;
@@ -15,6 +20,7 @@ pub type RemoteAddress = String;
pub enum RequestFlag { pub enum RequestFlag {
Connect = 0, Connect = 0,
Send = 1, Send = 1,
Query = 2,
} }
impl TryFrom<u8> for RequestFlag { impl TryFrom<u8> for RequestFlag {
@@ -24,6 +30,7 @@ impl TryFrom<u8> for RequestFlag {
match value { match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect), _ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send), _ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
_ if value == (RequestFlag::Query as u8) => Ok(Self::Query),
value => Err(RequestDeserializationError::UnknownRequestFlag { value }), value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
} }
} }
@@ -51,6 +58,15 @@ pub enum RequestDeserializationError {
#[error("malformed return address - {0}")] #[error("malformed return address - {0}")]
MalformedReturnAddress(RecipientFormattingError), MalformedReturnAddress(RecipientFormattingError),
#[error("failed to deserialize query request: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
#[error(transparent)]
InvalidSocketData(#[from] InsufficientSocketDataError),
} }
impl RequestDeserializationError { impl RequestDeserializationError {
@@ -59,7 +75,7 @@ impl RequestDeserializationError {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectRequest { pub struct ConnectRequest {
// TODO: is connection_id redundant now? // TODO: is connection_id redundant now?
pub conn_id: ConnectionId, pub conn_id: ConnectionId,
@@ -67,11 +83,15 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>, pub return_address: Option<Recipient>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendRequest { pub struct SendRequest {
pub conn_id: ConnectionId, pub data: SocketData,
pub data: Vec<u8>, }
pub local_closed: bool,
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryRequest {
OpenProxy,
Description,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -108,6 +128,13 @@ impl Serializable for Socks5Request {
} }
let protocol_version = Socks5ProtocolVersion::from(b[0]); let protocol_version = Socks5ProtocolVersion::from(b[0]);
if protocol_version > Self::max_supported_version() {
return Err(Socks5RequestError::UnsupportedProtocolVersion { protocol_version });
}
// TODO: handle the case then protocol version if less then the current one. Then we should
// make sure to only respond with the same version
Ok(Socks5Request { Ok(Socks5Request {
protocol_version, protocol_version,
content: Socks5RequestContent::try_from_bytes(&b[1..])?, content: Socks5RequestContent::try_from_bytes(&b[1..])?,
@@ -155,22 +182,27 @@ impl Socks5Request {
} }
} }
pub fn new_send( pub fn new_send(protocol_version: Socks5ProtocolVersion, data: SocketData) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_send(data),
}
}
pub fn new_query(
protocol_version: Socks5ProtocolVersion, protocol_version: Socks5ProtocolVersion,
conn_id: ConnectionId, query: QueryRequest,
data: Vec<u8>,
local_closed: bool,
) -> Socks5Request { ) -> Socks5Request {
Socks5Request { Socks5Request {
protocol_version, protocol_version,
content: Socks5RequestContent::new_send(conn_id, data, local_closed), content: Socks5RequestContent::Query(query),
} }
} }
} }
/// A request from a SOCKS5 client that a Nym Socks5 service provider should /// A request from a SOCKS5 client that a Nym Socks5 service provider should
/// take an action for an application using a (probably local) Nym Socks5 proxy. /// take an action for an application using a (probably local) Nym Socks5 proxy.
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Socks5RequestContent { pub enum Socks5RequestContent {
/// Start a new TCP connection to the specified `RemoteAddress` and send /// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection. /// the request data up the connection.
@@ -179,6 +211,8 @@ pub enum Socks5RequestContent {
/// Re-use an existing TCP connection, sending more request data up it. /// Re-use an existing TCP connection, sending more request data up it.
Send(SendRequest), Send(SendRequest),
Query(QueryRequest),
} }
impl Socks5RequestContent { impl Socks5RequestContent {
@@ -196,16 +230,8 @@ impl Socks5RequestContent {
} }
/// Construct a new Request::Send instance /// Construct a new Request::Send instance
pub fn new_send( pub fn new_send(data: SocketData) -> Socks5RequestContent {
conn_id: ConnectionId, Socks5RequestContent::Send(SendRequest { data })
data: Vec<u8>,
local_closed: bool,
) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
})
} }
/// Deserialize the request type, connection id, destination address and port, /// Deserialize the request type, connection id, destination address and port,
@@ -222,18 +248,27 @@ impl Socks5RequestContent {
/// The request_flag tells us whether this is a new connection request (`new_connect`), /// The request_flag tells us whether this is a new connection request (`new_connect`),
/// an already-established connection we should send up (`new_send`), or /// an already-established connection we should send up (`new_send`), or
/// a request to close an established connection (`new_close`). /// a request to close an established connection (`new_close`).
// connect:
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
//
// send:
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
// where DATA: SEQ || TRUE_DATA
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> { pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
// each request needs to at least contain flag and ConnectionId // each request needs to at least contain flag and ConnectionId
if b.is_empty() { if b.is_empty() {
return Err(RequestDeserializationError::NoData); return Err(RequestDeserializationError::NoData);
} }
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
match RequestFlag::try_from(b[0])? { match RequestFlag::try_from(b[0])? {
RequestFlag::Connect => { RequestFlag::Connect => {
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
let connect_request_bytes = &b[9..]; let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length // we need to be able to read at least 2 bytes that specify address length
@@ -278,15 +313,13 @@ impl Socks5RequestContent {
return_address, return_address,
)) ))
} }
RequestFlag::Send => { RequestFlag::Send => Ok(Socks5RequestContent::Send(SendRequest {
let local_closed = b[9] != 0; data: SocketData::try_from_request_bytes(&b[1..])?,
let data = b[10..].to_vec(); })),
RequestFlag::Query => {
Ok(Socks5RequestContent::Send(SendRequest { use bincode::Options;
conn_id, let query = make_bincode_serializer().deserialize(&b[1..])?;
data, Ok(Socks5RequestContent::Query(query))
local_closed,
}))
} }
} }
} }
@@ -313,10 +346,21 @@ impl Socks5RequestContent {
} }
} }
Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8) Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
.chain(req.conn_id.to_be_bytes().into_iter()) .chain(req.data.into_request_bytes_iter())
.chain(std::iter::once(req.local_closed as u8))
.chain(req.data.into_iter())
.collect(), .collect(),
Socks5RequestContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query request: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(RequestFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
} }
} }
} }
@@ -559,26 +603,7 @@ mod request_deserialization_tests {
#[test] #[test]
fn works_when_request_is_sized_properly_even_without_data() { fn works_when_request_is_sized_properly_even_without_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data // correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 0 bytes request data
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(Vec::<u8>::new(), data);
assert!(!local_closed)
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 3 bytes request data (all 255)
let request_bytes = [ let request_bytes = [
RequestFlag::Send as u8, RequestFlag::Send as u8,
1, 1,
@@ -590,6 +615,53 @@ mod request_deserialization_tests {
7, 7,
8, 8,
0, 0,
0,
0,
0,
0,
0,
0,
0,
1,
]
.to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest { data }) => {
assert_eq!(
u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
data.header.connection_id
);
assert!(!data.header.local_socket_closed);
assert_eq!(1, data.header.seq);
assert!(data.data.is_empty());
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 3 bytes request data (all 255)
let request_bytes = [
RequestFlag::Send as u8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
0,
0,
0,
0,
0,
0,
0,
1,
255, 255,
255, 255,
255, 255,
@@ -598,17 +670,39 @@ mod request_deserialization_tests {
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request { match request {
Socks5RequestContent::Send(SendRequest { Socks5RequestContent::Send(SendRequest { data }) => {
conn_id, assert_eq!(
data, u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
local_closed, data.header.connection_id
}) => { );
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id); assert!(data.header.local_socket_closed);
assert_eq!(vec![255, 255, 255], data); assert_eq!(1, data.header.seq);
assert!(!local_closed) assert_eq!(vec![255, 255, 255], data.data);
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
} }
#[cfg(test)]
mod serialize_query_request {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5RequestContent::Query(QueryRequest::OpenProxy);
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![2, 0]);
let description = Socks5RequestContent::Query(QueryRequest::Description);
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![2, 1]);
let open_proxy2 = Socks5RequestContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5RequestContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
} }
+104 -147
View File
@@ -1,8 +1,13 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError}; use crate::{
make_bincode_serializer, ConnectionId, InsufficientSocketDataError, SocketData,
Socks5ProtocolVersion, Socks5RequestError,
};
use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse}; use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse};
use serde::{Deserialize, Serialize};
use tap::TapFallible;
use thiserror::Error; use thiserror::Error;
// don't start tags from 0 for easier backwards compatibility since `NetworkData` // don't start tags from 0 for easier backwards compatibility since `NetworkData`
@@ -13,6 +18,7 @@ use thiserror::Error;
pub enum ResponseFlag { pub enum ResponseFlag {
NetworkData = 1, NetworkData = 1,
ConnectionError = 2, ConnectionError = 2,
Query = 3,
} }
impl TryFrom<u8> for ResponseFlag { impl TryFrom<u8> for ResponseFlag {
@@ -22,13 +28,20 @@ impl TryFrom<u8> for ResponseFlag {
match value { match value {
_ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData), _ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData),
_ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError), _ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError),
_ if value == (ResponseFlag::Query as u8) => Ok(Self::Query),
value => Err(ResponseDeserializationError::UnknownResponseFlag { value }), value => Err(ResponseDeserializationError::UnknownResponseFlag { value }),
} }
} }
} }
#[derive(Debug, Error, PartialEq, Eq)] #[derive(Debug, Error)]
pub enum ResponseDeserializationError { pub enum ResponseDeserializationError {
#[error("the network data was malformed: {source}")]
MalformedNetworkData {
#[from]
source: InsufficientSocketDataError,
},
#[error("not enough bytes to recover the connection id")] #[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort, ConnectionIdTooShort,
@@ -43,6 +56,12 @@ pub enum ResponseDeserializationError {
#[from] #[from]
source: std::string::FromUtf8Error, source: std::string::FromUtf8Error,
}, },
#[error("failed to deserialize query response: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
} }
#[derive(Debug)] #[derive(Debug)]
@@ -105,23 +124,14 @@ impl Socks5Response {
pub fn new_network_data( pub fn new_network_data(
protocol_version: Socks5ProtocolVersion, protocol_version: Socks5ProtocolVersion,
seq: u64,
connection_id: ConnectionId, connection_id: ConnectionId,
data: Vec<u8>, data: Vec<u8>,
is_closed: bool, is_closed: bool,
) -> Socks5Response { ) -> Socks5Response {
Socks5Response { Socks5Response {
protocol_version, protocol_version,
content: Socks5ResponseContent::new_network_data(connection_id, data, is_closed), content: Socks5ResponseContent::new_network_data(seq, connection_id, data, is_closed),
}
}
pub fn new_closed_empty(
protocol_version: Socks5ProtocolVersion,
connection_id: ConnectionId,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_closed_empty(connection_id),
} }
} }
@@ -135,25 +145,35 @@ impl Socks5Response {
content: Socks5ResponseContent::new_connection_error(connection_id, error_message), content: Socks5ResponseContent::new_connection_error(connection_id, error_message),
} }
} }
pub fn new_query(
protocol_version: Socks5ProtocolVersion,
query_response: QueryResponse,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::Query(query_response),
}
}
} }
#[derive(Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Socks5ResponseContent { pub enum Socks5ResponseContent {
NetworkData(NetworkData), NetworkData { content: SocketData },
ConnectionError(ConnectionError), ConnectionError(ConnectionError),
Query(QueryResponse),
} }
impl Socks5ResponseContent { impl Socks5ResponseContent {
pub fn new_network_data( pub fn new_network_data(
seq: u64,
connection_id: ConnectionId, connection_id: ConnectionId,
data: Vec<u8>, data: Vec<u8>,
is_closed: bool, is_closed: bool,
) -> Socks5ResponseContent { ) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new(connection_id, data, is_closed)) Socks5ResponseContent::NetworkData {
} content: SocketData::new(seq, connection_id, is_closed, data),
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new_closed_empty(connection_id))
} }
pub fn new_connection_error( pub fn new_connection_error(
@@ -165,9 +185,9 @@ impl Socks5ResponseContent {
pub fn into_bytes(self) -> Vec<u8> { pub fn into_bytes(self) -> Vec<u8> {
match self { match self {
Socks5ResponseContent::NetworkData(res) => { Socks5ResponseContent::NetworkData { content } => {
std::iter::once(ResponseFlag::NetworkData as u8) std::iter::once(ResponseFlag::NetworkData as u8)
.chain(res.into_bytes().into_iter()) .chain(content.into_response_bytes_iter())
.collect() .collect()
} }
Socks5ResponseContent::ConnectionError(res) => { Socks5ResponseContent::ConnectionError(res) => {
@@ -175,6 +195,18 @@ impl Socks5ResponseContent {
.chain(res.into_bytes().into_iter()) .chain(res.into_bytes().into_iter())
.collect() .collect()
} }
Socks5ResponseContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query response: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(ResponseFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
} }
} }
@@ -187,84 +219,29 @@ impl Socks5ResponseContent {
let response_flag = ResponseFlag::try_from(b[0])?; let response_flag = ResponseFlag::try_from(b[0])?;
match response_flag { match response_flag {
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData( ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData {
NetworkData::try_from_bytes(&b[1..])?, content: SocketData::try_from_response_bytes(&b[1..])?,
)), }),
ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError( ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError(
ConnectionError::try_from_bytes(&b[1..])?, ConnectionError::try_from_bytes(&b[1..])?,
)), )),
ResponseFlag::Query => {
use bincode::Options;
let query = make_bincode_serializer().deserialize(&b[1..])?;
Ok(Socks5ResponseContent::Query(query))
}
}
}
pub fn as_query(&self) -> Option<&QueryResponse> {
match self {
Socks5ResponseContent::Query(query) => Some(query),
_ => None,
} }
} }
} }
/// A remote network network data response retrieved by the Socks5 service provider. This #[derive(Clone, Debug, PartialEq, Eq)]
/// can be serialized and sent back through the mixnet to the requesting
/// application.
#[derive(Debug)]
pub struct NetworkData {
pub data: Vec<u8>,
pub connection_id: ConnectionId,
pub is_closed: bool,
}
impl NetworkData {
/// Constructor for responses
pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self {
NetworkData {
data,
connection_id,
is_closed,
}
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Self {
NetworkData {
data: vec![],
connection_id,
is_closed: false,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkData, ResponseDeserializationError> {
if b.is_empty() {
return Err(ResponseDeserializationError::NoData);
}
let is_closed = b[0] != 0;
if b.len() < 9 {
return Err(ResponseDeserializationError::ConnectionIdTooShort);
}
let mut connection_id_bytes = b.to_vec();
let data = connection_id_bytes.split_off(9);
let connection_id = u64::from_be_bytes([
connection_id_bytes[1],
connection_id_bytes[2],
connection_id_bytes[3],
connection_id_bytes[4],
connection_id_bytes[5],
connection_id_bytes[6],
connection_id_bytes[7],
connection_id_bytes[8],
]);
let response = NetworkData::new(connection_id, data, is_closed);
Ok(response)
}
/// Serializes the response into bytes so that it can be sent back through
/// the mixnet to the requesting application.
pub fn into_bytes(self) -> Vec<u8> {
std::iter::once(self.is_closed as u8)
.chain(self.connection_id.to_be_bytes().iter().cloned())
.chain(self.data.into_iter())
.collect()
}
}
#[derive(Debug)]
pub struct ConnectionError { pub struct ConnectionError {
pub connection_id: ConnectionId, pub connection_id: ConnectionId,
pub network_requester_error: String, pub network_requester_error: String,
@@ -318,62 +295,16 @@ impl ConnectionError {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryResponse {
OpenProxy(bool),
Description(String),
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[cfg(test)]
mod constructing_socks5_data_responses_from_bytes {
use super::*;
#[test]
fn fails_when_zero_bytes_are_supplied() {
let response_bytes = Vec::new();
assert_eq!(
ResponseDeserializationError::NoData,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn fails_when_connection_id_bytes_are_too_short() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
assert_eq!(
ResponseDeserializationError::ConnectionIdTooShort,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn works_when_there_is_no_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
Vec::new(),
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
#[test]
fn works_when_there_is_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
vec![255, 255, 255],
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
}
#[cfg(test)] #[cfg(test)]
mod connection_error_response_serde_tests { mod connection_error_response_serde_tests {
use super::*; use super::*;
@@ -396,11 +327,14 @@ mod tests {
#[test] #[test]
fn deserialization_errors() { fn deserialization_errors() {
let err = ConnectionError::try_from_bytes(&[]).err().unwrap(); let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
assert_eq!(err, ResponseDeserializationError::NoData); assert!(matches!(err, ResponseDeserializationError::NoData));
let bytes: [u8; 5] = [1, 2, 3, 4, 5]; let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap(); let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort); assert!(matches!(
err,
ResponseDeserializationError::ConnectionIdTooShort
));
let bytes: Vec<u8> = 42u64 let bytes: Vec<u8> = 42u64
.to_be_bytes() .to_be_bytes()
@@ -414,4 +348,27 @@ mod tests {
)); ));
} }
} }
#[cfg(test)]
mod serialize_query_response {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5ResponseContent::Query(QueryResponse::OpenProxy(true));
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![3, 0, 1]);
let description =
Socks5ResponseContent::Query(QueryResponse::Description("foo".to_string()));
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![3, 1, 3, 102, 111, 111]);
let open_proxy2 = Socks5ResponseContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5ResponseContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
} }
-6
View File
@@ -81,7 +81,6 @@ impl GatewayBond {
pub struct GatewayNodeDetailsResponse { pub struct GatewayNodeDetailsResponse {
pub identity_key: String, pub identity_key: String,
pub sphinx_key: String, pub sphinx_key: String,
pub announce_address: String,
pub bind_address: String, pub bind_address: String,
pub version: String, pub version: String,
pub mix_port: u16, pub mix_port: u16,
@@ -93,11 +92,6 @@ impl fmt::Display for GatewayNodeDetailsResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Identity Key: {}", self.identity_key)?; writeln!(f, "Identity Key: {}", self.identity_key)?;
writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; writeln!(f, "Sphinx Key: {}", self.sphinx_key)?;
writeln!(
f,
"Host: {} (bind address: {})",
self.announce_address, self.bind_address
)?;
writeln!(f, "Version: {}", self.version)?; writeln!(f, "Version: {}", self.version)?;
writeln!( writeln!(
f, f,
+4 -11
View File
@@ -13,6 +13,7 @@ use nym_mixnet_contract_common::{
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::net::IpAddr;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr( #[cfg_attr(
@@ -167,31 +168,23 @@ impl MixNodeCostParams {
pub struct MixnodeNodeDetailsResponse { pub struct MixnodeNodeDetailsResponse {
pub identity_key: String, pub identity_key: String,
pub sphinx_key: String, pub sphinx_key: String,
pub announce_address: String, pub bind_address: IpAddr,
pub bind_address: String,
pub version: String, pub version: String,
pub mix_port: u16, pub mix_port: u16,
pub http_api_port: u16, pub http_api_port: u16,
pub verloc_port: u16, pub verloc_port: u16,
pub wallet_address: Option<String>,
} }
impl fmt::Display for MixnodeNodeDetailsResponse { impl fmt::Display for MixnodeNodeDetailsResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let wallet_address = self.wallet_address.clone().unwrap_or_default();
writeln!(f, "Identity Key: {}", self.identity_key)?; writeln!(f, "Identity Key: {}", self.identity_key)?;
writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; writeln!(f, "Sphinx Key: {}", self.sphinx_key)?;
writeln!( writeln!(f, "Host: {}", self.bind_address)?;
f,
"Host: {} (bind address: {})",
self.announce_address, self.bind_address
)?;
writeln!(f, "Version: {}", self.version)?; writeln!(f, "Version: {}", self.version)?;
writeln!( writeln!(
f, f,
"Mix Port: {}, Verloc port: {}, Http Port: {}\n", "Mix Port: {}, Verloc port: {}, Http Port: {}\n",
self.mix_port, self.verloc_port, self.http_api_port self.mix_port, self.verloc_port, self.http_api_port
)?; )
writeln!(f, "You are bonding to wallet address: {wallet_address}\n\n")
} }
} }

Some files were not shown because too many files have changed in this diff Show More