Release/v0.9.0 (#453)

* Feature/wasm update (#341)

* Split text and binary client apis

* Very initial attempt at new serialization

* Defined ser+de for Recipient and ReplySURB

* Response errors

* builds with changes

* Working WS API + moved to separate crate

* updated python examples

* Fixed parsing bug

* Updated go examples

* Updated rust examples

* formatting

* Removed unused imports

* dependency updates

* Further dependency changes

* nymsphinx exposingn framing only if not in wasm32

* Cargo lock changes before develop merge

* Pending work

* Actually sending and receiving websocket from rust!

* more WIP

* Initial wasm client + establishing shared key with gateway!

* Splitting and sending a message!

* WIP

* WIP

* Initial wasm-ification of the gateway client

* Passing reconstruction result to js callback!

* Initial WASM cleaning pass

* Dependency pruning

* Moved processing loop to received_processor + at least ack unwrappingn

* Post merge fix

* Kinda updated react example

* Old print statement removed

* Removed yarn.lock

* Fixed building issue for other clients

* Fixed travis test command

* Updated wasm publishing instructions

* Feature/instant sending (#359)

* Ability to set client in vpn mode

* Connection handler for mixnode

* Initial vpn mode for mixes

* Updated SphinxCodec to contain more metadata

* Renaming

* Removed handle from mixnet client and introduced forwarder

* Mixnode using new forwarder

* Mixnode common module containing shared packet processing

* ibid. incorporated inside mixnode

* New processing for gateway

* Type cleanup

* Wasm fix

* Fixed client config

* Fixed mixnode runtime issues

* Formatting

* Client re-using secret on 'normal' packets

* Using the same key for acks

* WIP

* vpn key manager cleanup

* wasm fix

* VPN_KEY_REUSE_LIMIT moved to config

* Moved AckDelayQueue to separate common crate

* Key cache invalidator

* Updated dashmap used in gateway

* Old typo

* Additional comment

* Cargo fmt

* Fixed tests

* Sphinx update

* cache ttl as config option

* Cargo fmt

* Feature/network monitor (#369)

* WIP commit

* Further into errors (WIP)

* WIP commit, more stuff compiling, but now we switch to sneaky Jedrzej trick

* Websocket connections starting to work

* WIP

* Constructing the socket stream in one go

* Nicer connections. Getting topology now works.

* Fixing startup message

* Injecting directory uri

* Injecting good mixndodes

* Deleting old healthcheck

* Starting to wrap gateway client

* Splitting out good topology contstruction

* Starting to breathe finally

* Fixed layer assignment error

* Starting gateway client in a better spot

* Cleanup

* Renamed construct() to new()

* Injected channels, removed websockets

* Added a MixnetListener to deal with returned packet traffic

* Simplification

* Renaming and commenting a few things

* Renamed temp variables

* Fixed variable names

* Made errors a bit more explicit on message reconstruction

* Added the mixmining route / object to the directory server client

* Recipient is always "me", a bit easier to understand what's going on

* Subbing in test nodes works

* Using QA directory

* Noting which directory server we're using at monitor start

* Adding Debug to MixStatus so we can print it more easily

* Prettification of startup messages

* Notifying directory of successful packet receipt

* All nodes now being tested

* Now able to do timer-based test runs and detect the last node tested

* Renamed mixnet_listener to notifier

* Moved message chunking into its own home

* Starting to pull out packet sends

* Basic functionality working!

* Enabled timer runs forever

* License notices

* Depdendency cleanup

* Import cleanup

* Moved to implicit tokio runtime

* Removed some unnecessary data clones

* Fixed monitor doing two runs at startup rather than one

* dalek version updates

* This should have been split but got carried away

* Initial ipv6 support

* Don't test outdated nodes

* Added network monitor to default workspace members

* Actually sending reports

* Batch-sending mix status

* Cargo fmt

* Minor cleanup

* Malformed Cargo.lock

* Post-merge fixes

* Defined constant in main.rs for enabling detailed reporting

* Updated package versions of the system

* Checking version compatibility on startup

Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com>

* Added sphinx socks to default workspace members (#358)

* Feature/version in config (#376)

* Added version field to config files

* Removed built dependency

* Missing non-removed call to built

* Corrected version on client-core (#377)

* Feature/remove validator (#380)

* Replacing Rust validator sketch with Cosmos-based code.

* Removing old build instructions

* go fmt on generated code

* Removing build files for github and pi

* Starting transplant surgery

* Directory now running in validator, sans metrics

* Updating go sum

* A partly-working directory startup integrated into the nymcli app.

* Removing the Swagger docs header from nymcli, it can live in server.go

* Working swagger docs.

* Updating test title on Swagger docs

* Directory server now integrated into validator (primitive but working)

* Noted location of mixmining reports database.

* Bumped golang version

* Removing validator cargo files.

* Moving directory API init method into the nymd module

* Ditching a test which is causing tooling problems - presence will go away soon anyway

* Removing the validator and merging develop

* Update README.md

* Update README.md

* Feature/upgrade command (#381)

* Exposed version parsing from version checker

* Ability to upgrade mixnode from 0.8.X to 0.9.0

* Ability to upgrade gateway from 0.8.X to 0.9.0

* Ability to upgrade native client from 0.8.X to 0.9.0

* Ability to upgrade socks5 client from 0.8.X to 0.9.0

* Typo

* Minor renaming

* Preventing upgrade if current is not a release version

* Additional upgrade restrictions

* Corrected version bound

* Feature/mix ed25519 identity (#388)

* Introduced identity keypair to mixnode

* Updated upgrade 0.9.0 mix upgrade instructions

* Printing failed upgrade notice when it failed

* Moved printing upgrade start notice to beginning of function

* Feature/double init prevention (#386)

* Updated NymConfig trait making id always obligatory. Also added extra default method implementations

* Preventing init on mixnodes that were initialised before

* Quotes before mix id

* Preventing init on gateways that were initialised before

* Preventing init on native clients that were initialised before

* Preventing init on socks5 clients that were initialised before

* Feature/duration cleanup (#391)

* Mixnode using Duration for time intervals + human readable config fields for them

* Removed old comment

* Native client using Duration for time intervals + human readable config fields for them

* Socks5 client using Duration for time intervals + human readable config fields for them

* Gateway using Duration for time intervals + human readable config fields for them

* Config compatibility with 0.8.0 debug values

* Fixed previous merge issue

* Moving subsidiary functions down below main so we can see where thing start

* Feature/fix dependabot alerts (#393)

* Fixing a dependabot alert

* Fixing npm audit failures in js example for webassembly client

* Fixed some more npm audit problems

* Explicitly handling base58 key recovery errors (#396)

* Fix compiler warnings for unneeded mut (#398)

* moved new_v4_with_node to test only section (#392)

* moved new_v4_with_node to test only section

* cargo fmt

* Feature/directory server transition (#401)

* Initial changes to validator client API

* Updated models

* GatewayRegistrationInfo constructor

* Change validator topology to convert into NymTopology without failure

* Mixnode registering and unregistering presence

* Directory -> Validator renamings + adjustments

* Updated upgrade command for mixnode

* Extracted metrics part of directory client into separate library

* Removed no longer needed traits

* Integrated new metrics client into mixnode

* Introduced the same set of changes to the gateway

* Getting active topology in client core via validator client

* Updated clients to get correct topology

* Introduced mix mining endpoints to validator client

* Network monitor using validator client

* Removed directory client

* Updated wasm client

* Temporarily disabled the test

* Checking ok status for validator client response

* Updated upgrade command for clients

* Allowing using old presence directory as new validator endpoint for mixnodes and gateways

* Fixed tests in non-default crates

* Feature/validator api update (#402)

* Updated validator API routes

* missing comment

* Feature/wasm client fix (#399)

* Disabling wasm optimization for the moment, it isn't working on my machine

* Updating the React example back to a working state, 0.8.1 npm package is published.

* Merging develop into bugfix

* Upgrading react-scripts to fix a major vulnerability

* Using metrics interval received from server (#403)

* Using metrics interval received from server

* Test fix

* Feature/error on noninit (#404)

* More graceful shutdown in case of uninitialised client/node

* Performing version check on binary `run`

* Feature/no run config flag (#405)

* Removed `--config` parameter in `run` commands

* Removed old tests that were doing disk IO

* Renamed directory arguments to validator (#408)

* Feature/max retry (#409)

* Maximum number of allowed reconnection attempts

* Requiring initial internode connection to be successful

* Decreased logging level for failing to establish initial connection

* Feature/controlled reinit (#410)

* Allowing init with existing id but keeps existing keys

* Removed unused imports

* Removed debug print statement (#411)

* Updated upgrade commands to set new default validator (#413)

* Feature/network monitor file topology (#412)

* Network monitor loading 'good' topology from files instead

* Update .gitignore

* Passing address of validator as an argument

* Made detailed report const flag into an argument

* Flag to start network requester in open proxy mode (#418)

* Renamed 'sphinx-socks' to 'network-requester' (#417)

* Added option to set incentives address during mix and gateway init (#419)

* Updated validator topology (#420)

* Bugfix/upgrade fix (#421)

* Added default for incentives address when deserializing

* Ability to optionally pass incentives address during upgrade

* Feature/socks improvements (#423)

* Changed AvailableReader from Future to Stream

* comment formatting

* WIP

* Maximum number of bytes read per poll

* More detailed socks5 printing

* Split up socks5 service provider runner

* Fixed closing proxy too soon on one side

* Additional log information

* Printing connection id in log target

* Adjusted some constants

* Target field in log

* Removed dead code

* Decreased logging level

* Explicitly seperated 'Send' and 'Connect' request + data buffering

* Temporarily commented out test that fails due to not understanding limitations of tokio test io builder

* Fixed socks5 tests

* Starting cover traffic stream under correct condition (#422)

* Fixed bunch of clippy warnings (#427)

* Updated default sending rates (#430)

* Feature/explorer (#431)

* Initial commit of the new dashboard code.

* Periodically grabbing topology json

* Pulling file saving out into its own module

* Ignoring downloaded topology file

* Moved everything public into a public folder

* Refreshing the mixmining report

* Mounting static files from /public

* Including mixminiming report grabber

* Leaving the route in place to pick up later. It's not used right now.

* Removing json download from git

* Ignoring topology download

* Moving recurrent jobs in to a jobs module

* Adding websocket dependencies

* Starting to get client/server websocket functionality running.

* Fixing unused imports

* Separating client and server functionality a bit more cleanly

* WIP to sketch out the ws client and server a bit more

* Initial metrics broadcaster

* Import fixup

* Spawning rocket in tokio task

* Removed outdated comment

* removed the js file

Co-authored-by: Dave <futurechimp@users.noreply.github.com>

* Bugfix/metrics fixes (#434)

* Updated dashboard html file

* Changed explorer to listen on the same port as old dashboard

* Using correct field names in js file

* Sending correct key in metrics

* Added nym prefix to binary names (#436)

* Passing validator base url as an argument (#439)

* Spawning rocket as a blocking task (#440)

* Update main.js (#441)

* Update main.js

* Changed websocket port to 1648

* Changing default validator location to make it clear we're testnet (#437)

* Changing default validator location to make it clear we're on a testnet here.

* Updated validator url

* Updated metrics url

Co-authored-by: jstuczyn <jedrzej.stuczynski@gmail.com>

* Adding keybase to allowed.list.sample (#442)

* Updated nym-run gateway id (#445)

* Adjusted 'fastmode' settings (#444)

* Added validators to dashboard + validator and block count (#443)

* Slightly friendlier upgrade argument description (#446)

* Slightly friendlier upgrade argument description

* Added right format examples

* Explorer public folder being relative to the binary (#447)

* Explorer public folder being relative to the binary

* Qualifying the public mounting path

Co-authored-by: Dave <futurechimp@users.noreply.github.com>

* Extra argument to specify metrics websocket + long attribute (#448)

* Extra argument to specify metrics websocket + long attribute

* Default testnet values

* Fixed the URL of the websocket (#449)

* Removed hardcoded 'good gateways' in favour of pseusorandom choice from the active set (#450)

* Adding graceful de-registration to service

* Removed incentives form url (#451)

* Updated version numbers

* Updated generate changelog script

* Updated changelog

* wasm examples fixes

Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
Co-authored-by: Ethan Frey <ethanfrey@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2020-11-13 12:44:58 +00:00
committed by GitHub
parent d05b313a99
commit 2fa5c3c3b7
357 changed files with 39278 additions and 13268 deletions
+7
View File
@@ -15,3 +15,10 @@ scripts/run_mix.sh
scripts/start_local_tmux_network.sh
/.floo
/.flooignore
qa-v4-topology.json
qa-v6-topology.json
v4-topology.json
v6-topology.json
/explorer/downloads/topology.json
/explorer/public/downloads/mixmining.json
/explorer/public/downloads/topology.json
+110 -2
View File
@@ -1,5 +1,113 @@
# Changelog
## [v0.9.0](https://github.com/nymtech/nym/tree/v0.9.0) (2020-11-13)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.8.1...v0.9.0)
The main features of this release are:
* a reputation tracking system which starts to link node reputation to quality of service
* a new component, the `nym-network-monitor`, which tracks whether nodes are working properly and providing good service
* automatic node registration and de-registration at node startup
* working Cosmos validators with a `nym` token
* starting to decentralize the old directory server into the validators
* a new block explorer at https://testnet-explorer.nymtech.net which looks the same as the old dashboard but is the basis of something much more advanced. It can be run by anyone.
* de-coupling metrics collection from directory services to make the system scale better overall
* reliability and performance improvements for mixnode networking
See the changelog for detailed release notes.
**Implemented enhancements:**
- Nicer error if trying to run an uninitialised client/node [\#389](https://github.com/nymtech/nym/issues/389)
- Gateway announcement [\#383](https://github.com/nymtech/nym/issues/383)
- Add init flag for incentives address [\#382](https://github.com/nymtech/nym/issues/382)
- Ed25519 Identity Keys for Mixnodes [\#379](https://github.com/nymtech/nym/issues/379)
- Introduce version field to config files [\#375](https://github.com/nymtech/nym/issues/375)
- Change `init` to not blow away existing keys \(if exist\) [\#368](https://github.com/nymtech/nym/issues/368)
- Introduce an explicit `upgrade` command [\#367](https://github.com/nymtech/nym/issues/367)
- Show remote hostname in socks5 connection messages [\#365](https://github.com/nymtech/nym/issues/365)
- Make all `const` duration values more explicit. [\#333](https://github.com/nymtech/nym/issues/333)
- Feature/socks improvements [\#423](https://github.com/nymtech/nym/pull/423) ([jstuczyn](https://github.com/jstuczyn))
- Feature/instant sending [\#359](https://github.com/nymtech/nym/pull/359) ([jstuczyn](https://github.com/jstuczyn))
**Fixed bugs:**
- React wasm example not compiling [\#394](https://github.com/nymtech/nym/issues/394)
- Update main.js [\#441](https://github.com/nymtech/nym/pull/441) ([jstuczyn](https://github.com/jstuczyn))
- Bugfix/metrics fixes [\#434](https://github.com/nymtech/nym/pull/434) ([jstuczyn](https://github.com/jstuczyn))
- Bugfix/upgrade fix [\#421](https://github.com/nymtech/nym/pull/421) ([jstuczyn](https://github.com/jstuczyn))
- Explicitly handling base58 key recovery errors [\#396](https://github.com/nymtech/nym/pull/396) ([jstuczyn](https://github.com/jstuczyn))
- Corrected version on client-core [\#377](https://github.com/nymtech/nym/pull/377) ([jstuczyn](https://github.com/jstuczyn))
**Closed issues:**
- Make validator URL configurable [\#438](https://github.com/nymtech/nym/issues/438)
- Change default directory location [\#432](https://github.com/nymtech/nym/issues/432)
- Crank up the default bandwidth settings. [\#429](https://github.com/nymtech/nym/issues/429)
- Change "sphinx-socks" to "nym-requester" [\#428](https://github.com/nymtech/nym/issues/428)
- Clients should use only "active" nodes [\#390](https://github.com/nymtech/nym/issues/390)
- Allow persistently changing config values from command line [\#387](https://github.com/nymtech/nym/issues/387)
- Remove `--config` flag in `run` [\#385](https://github.com/nymtech/nym/issues/385)
- Metrics server should return a metrics rate value [\#374](https://github.com/nymtech/nym/issues/374)
- Integer staking [\#372](https://github.com/nymtech/nym/issues/372)
- Mixnode and gateway blockchain registration [\#371](https://github.com/nymtech/nym/issues/371)
- Remove presence notifications [\#370](https://github.com/nymtech/nym/issues/370)
- Handle invalid base58 encoding for asymmetric key recovery \(encryption and identity\) [\#285](https://github.com/nymtech/nym/issues/285)
- Socks5 nym client + bitcoin service provider [\#254](https://github.com/nymtech/nym/issues/254)
- Message reception in webassembly client [\#204](https://github.com/nymtech/nym/issues/204)
- Simplest possible staking system [\#157](https://github.com/nymtech/nym/issues/157)
- Validator should hold topology [\#77](https://github.com/nymtech/nym/issues/77)
**Merged pull requests:**
- Removed incentives form url [\#451](https://github.com/nymtech/nym/pull/451) ([jstuczyn](https://github.com/jstuczyn))
- Removed hardcoded 'good gateways' in favour of pseusorandom choice fr… [\#450](https://github.com/nymtech/nym/pull/450) ([jstuczyn](https://github.com/jstuczyn))
- Fixed the URL of the websocket [\#449](https://github.com/nymtech/nym/pull/449) ([futurechimp](https://github.com/futurechimp))
- Extra argument to specify metrics websocket + long attribute [\#448](https://github.com/nymtech/nym/pull/448) ([jstuczyn](https://github.com/jstuczyn))
- Explorer public folder being relative to the binary [\#447](https://github.com/nymtech/nym/pull/447) ([jstuczyn](https://github.com/jstuczyn))
- Slightly friendlier upgrade argument description [\#446](https://github.com/nymtech/nym/pull/446) ([jstuczyn](https://github.com/jstuczyn))
- Updated nym-run gateway id [\#445](https://github.com/nymtech/nym/pull/445) ([jstuczyn](https://github.com/jstuczyn))
- Adjusted 'fastmode' settings [\#444](https://github.com/nymtech/nym/pull/444) ([jstuczyn](https://github.com/jstuczyn))
- Added validators to dashboard + validator and block count [\#443](https://github.com/nymtech/nym/pull/443) ([jstuczyn](https://github.com/jstuczyn))
- Adding keybase to allowed.list.sample [\#442](https://github.com/nymtech/nym/pull/442) ([futurechimp](https://github.com/futurechimp))
- Spawning rocket as a blocking task [\#440](https://github.com/nymtech/nym/pull/440) ([jstuczyn](https://github.com/jstuczyn))
- Passing validator base url as an argument [\#439](https://github.com/nymtech/nym/pull/439) ([jstuczyn](https://github.com/jstuczyn))
- Changing default validator location to make it clear we're testnet [\#437](https://github.com/nymtech/nym/pull/437) ([futurechimp](https://github.com/futurechimp))
- Added nym prefix to binary names [\#436](https://github.com/nymtech/nym/pull/436) ([jstuczyn](https://github.com/jstuczyn))
- Feature/explorer [\#431](https://github.com/nymtech/nym/pull/431) ([jstuczyn](https://github.com/jstuczyn))
- Updated default sending rates [\#430](https://github.com/nymtech/nym/pull/430) ([jstuczyn](https://github.com/jstuczyn))
- Fixed bunch of clippy warnings [\#427](https://github.com/nymtech/nym/pull/427) ([jstuczyn](https://github.com/jstuczyn))
- Starting cover traffic stream under correct condition [\#422](https://github.com/nymtech/nym/pull/422) ([jstuczyn](https://github.com/jstuczyn))
- Updated validator topology [\#420](https://github.com/nymtech/nym/pull/420) ([jstuczyn](https://github.com/jstuczyn))
- Added option to set incentives address during mix and gateway init [\#419](https://github.com/nymtech/nym/pull/419) ([jstuczyn](https://github.com/jstuczyn))
- Flag to start network requester in open proxy mode [\#418](https://github.com/nymtech/nym/pull/418) ([jstuczyn](https://github.com/jstuczyn))
- Renamed 'sphinx-socks' to 'network-requester' [\#417](https://github.com/nymtech/nym/pull/417) ([jstuczyn](https://github.com/jstuczyn))
- Updated upgrade commands to set new default validator [\#413](https://github.com/nymtech/nym/pull/413) ([jstuczyn](https://github.com/jstuczyn))
- Feature/network monitor file topology [\#412](https://github.com/nymtech/nym/pull/412) ([jstuczyn](https://github.com/jstuczyn))
- Removed debug print statement [\#411](https://github.com/nymtech/nym/pull/411) ([jstuczyn](https://github.com/jstuczyn))
- Feature/controlled reinit [\#410](https://github.com/nymtech/nym/pull/410) ([jstuczyn](https://github.com/jstuczyn))
- Feature/max retry [\#409](https://github.com/nymtech/nym/pull/409) ([jstuczyn](https://github.com/jstuczyn))
- Renamed directory arguments to validator [\#408](https://github.com/nymtech/nym/pull/408) ([jstuczyn](https://github.com/jstuczyn))
- Feature/no run config flag [\#405](https://github.com/nymtech/nym/pull/405) ([jstuczyn](https://github.com/jstuczyn))
- Feature/error on noninit [\#404](https://github.com/nymtech/nym/pull/404) ([jstuczyn](https://github.com/jstuczyn))
- Using metrics interval received from server [\#403](https://github.com/nymtech/nym/pull/403) ([jstuczyn](https://github.com/jstuczyn))
- Feature/validator api update [\#402](https://github.com/nymtech/nym/pull/402) ([jstuczyn](https://github.com/jstuczyn))
- Feature/directory server transition [\#401](https://github.com/nymtech/nym/pull/401) ([jstuczyn](https://github.com/jstuczyn))
- Feature/wasm client fix [\#399](https://github.com/nymtech/nym/pull/399) ([futurechimp](https://github.com/futurechimp))
- Fix compiler warnings for unneeded mut [\#398](https://github.com/nymtech/nym/pull/398) ([ethanfrey](https://github.com/ethanfrey))
- Feature/fix dependabot alerts [\#393](https://github.com/nymtech/nym/pull/393) ([futurechimp](https://github.com/futurechimp))
- moved new\_v4\_with\_node to test only section [\#392](https://github.com/nymtech/nym/pull/392) ([jstuczyn](https://github.com/jstuczyn))
- Feature/duration cleanup [\#391](https://github.com/nymtech/nym/pull/391) ([jstuczyn](https://github.com/jstuczyn))
- Feature/mix ed25519 identity [\#388](https://github.com/nymtech/nym/pull/388) ([jstuczyn](https://github.com/jstuczyn))
- Feature/double init prevention [\#386](https://github.com/nymtech/nym/pull/386) ([jstuczyn](https://github.com/jstuczyn))
- Feature/upgrade command [\#381](https://github.com/nymtech/nym/pull/381) ([jstuczyn](https://github.com/jstuczyn))
- Feature/remove validator [\#380](https://github.com/nymtech/nym/pull/380) ([futurechimp](https://github.com/futurechimp))
- Feature/version in config [\#376](https://github.com/nymtech/nym/pull/376) ([jstuczyn](https://github.com/jstuczyn))
- Feature/network monitor [\#369](https://github.com/nymtech/nym/pull/369) ([jstuczyn](https://github.com/jstuczyn))
- Added sphinx socks to default workspace members [\#358](https://github.com/nymtech/nym/pull/358) ([jstuczyn](https://github.com/jstuczyn))
- Feature/wasm update [\#341](https://github.com/nymtech/nym/pull/341) ([jstuczyn](https://github.com/jstuczyn))
## [v0.8.1](https://github.com/nymtech/nym/tree/v0.8.1) (2020-09-28)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.8.0...v0.8.1)
@@ -16,7 +124,7 @@
**Merged pull requests:**
- Feature/wasm update [\#341](https://github.com/nymtech/nym/pull/341) ([jstuczyn](https://github.com/jstuczyn))
- Hotfix/0.8.1 [\#357](https://github.com/nymtech/nym/pull/357) ([jstuczyn](https://github.com/jstuczyn))
## [v0.8.0](https://github.com/nymtech/nym/tree/v0.8.0) (2020-09-10)
@@ -332,6 +440,7 @@ This release fixes bugs in v0.5.0. All testnet node operators are advised to up
**Merged pull requests:**
- Bugfix/healthcheck on provided topology [\#108](https://github.com/nymtech/nym/pull/108) ([jstuczyn](https://github.com/jstuczyn))
- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp))
## [v0.4.0](https://github.com/nymtech/nym/tree/v0.4.0) (2020-01-28)
@@ -382,7 +491,6 @@ This release also integrates a health-checker and network topology refresh into
- Feature/base58 [\#102](https://github.com/nymtech/nym/pull/102) ([futurechimp](https://github.com/futurechimp))
- Feature/panic improvements [\#101](https://github.com/nymtech/nym/pull/101) ([jstuczyn](https://github.com/jstuczyn))
- Feature/fix sphinx unwraps [\#100](https://github.com/nymtech/nym/pull/100) ([futurechimp](https://github.com/futurechimp))
- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp))
- Feature/version filtering improvements [\#96](https://github.com/nymtech/nym/pull/96) ([futurechimp](https://github.com/futurechimp))
- Feature/refreshing topology [\#94](https://github.com/nymtech/nym/pull/94) ([jstuczyn](https://github.com/jstuczyn))
- Feature/consistent logging [\#93](https://github.com/nymtech/nym/pull/93) ([futurechimp](https://github.com/futurechimp))
Generated
+262 -907
View File
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -13,19 +13,21 @@ members = [
"clients/socks5",
"clients/webassembly",
"clients/client-core",
"common/client-libs/directory-client",
"common/client-libs/directory-client/models",
"common/client-libs/gateway-client",
"common/client-libs/metrics-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/config",
"common/crypto",
"common/mixnode-common",
"common/nonexhaustive-delayqueue",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
"common/nymsphinx/anonymous-replies",
"common/nymsphinx/chunking",
"common/nymsphinx/cover",
"common/nymsphinx/forwarding",
"common/nymsphinx/framing",
"common/nymsphinx/params",
"common/nymsphinx/types",
@@ -36,9 +38,9 @@ members = [
"common/wasm-utils",
"gateway",
"gateway/gateway-requests",
"service-providers/sphinx-socks",
"mixnode",
"validator",
"network-monitor",
"service-providers/network-requester",
]
default-members = [
@@ -46,6 +48,9 @@ default-members = [
"clients/socks5",
# "clients/webassembly",
"gateway",
"service-providers/network-requester",
"mixnode",
"validator",
"network-monitor",
]
exclude = ["explorer"]
+2 -3
View File
@@ -1,13 +1,12 @@
## The Nym Privacy Platform
This repository contains the full Nym platform.
This repository contains the Nym mixnet.
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* nym-sfw-provider - a store-and-forward service provider. The provider acts sort of like a mailbox for mixnet messages.
* nym-validator - currently just starting development. Handles consensus ordering of transactions, mixmining, and coconut credential generation and validation.
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
[![Build Status](https://travis-ci.com/nymtech/nym.svg?branch=develop)](https://travis-ci.com/nymtech/nym)
+4 -6
View File
@@ -1,7 +1,6 @@
[package]
build = "build.rs"
name = "client-core"
version = "0.8.0-dev"
version = "0.9.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
@@ -10,6 +9,7 @@ edition = "2018"
[dependencies]
dirs = "2.0.2"
futures = "0.3.1"
humantime-serde = "1.0.1"
log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0.104", features = ["derive"] }
@@ -19,15 +19,13 @@ tokio = { version = "0.2", features = ["full"] }
# internal
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
directory-client = { path = "../../common/client-libs/directory-client" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
[build-dependencies]
built = "0.4.3"
validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies]
tempfile = "3.1.0"
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The file has been placed there by the build script.
include!(concat!(env!("OUT_DIR"), "/built.rs"));
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::topology_control::TopologyAccessor;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
@@ -50,7 +50,7 @@ where
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: MixMessageSender,
mix_tx: BatchMixMessageSender,
/// Represents full address of this client.
our_full_destination: Recipient,
@@ -101,7 +101,7 @@ impl LoopCoverTrafficStream<OsRng> {
average_ack_delay: time::Duration,
average_packet_delay: time::Duration,
average_cover_message_sending_delay: time::Duration,
mix_tx: MixMessageSender,
mix_tx: BatchMixMessageSender,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
) -> Self {
@@ -152,9 +152,7 @@ impl LoopCoverTrafficStream<OsRng> {
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx
.unbounded_send(MixMessage::new(cover_message.0, cover_message.1))
.unwrap();
self.mix_tx.unbounded_send(vec![cover_message]).unwrap();
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
@@ -173,7 +171,7 @@ impl LoopCoverTrafficStream<OsRng> {
self.average_cover_message_sending_delay,
));
while let Some(_) = self.next().await {
while self.next().await.is_some() {
self.on_new_message().await;
}
}
+27 -24
View File
@@ -16,19 +16,12 @@ use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use nymsphinx::forwarding::packet::MixPacket;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
pub struct MixMessage(NymNodeRoutingAddress, SphinxPacket);
pub type MixMessageSender = mpsc::UnboundedSender<MixMessage>;
pub type MixMessageReceiver = mpsc::UnboundedReceiver<MixMessage>;
impl MixMessage {
pub fn new(address: NymNodeRoutingAddress, packet: SphinxPacket) -> Self {
MixMessage(address, packet)
}
}
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver<Vec<MixPacket>>;
const MAX_FAILURE_COUNT: usize = 100;
@@ -36,7 +29,7 @@ pub struct MixTrafficController {
// TODO: most likely to be replaced by some higher level construct as
// later on gateway_client will need to be accessible by other entities
gateway_client: GatewayClient,
mix_rx: MixMessageReceiver,
mix_rx: BatchMixMessageReceiver,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
@@ -44,7 +37,10 @@ pub struct MixTrafficController {
}
impl MixTrafficController {
pub fn new(mix_rx: MixMessageReceiver, gateway_client: GatewayClient) -> MixTrafficController {
pub fn new(
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) -> MixTrafficController {
MixTrafficController {
gateway_client,
mix_rx,
@@ -52,31 +48,38 @@ impl MixTrafficController {
}
}
async fn on_message(&mut self, mix_message: MixMessage) {
debug!("Got a mix_message for {:?}", mix_message.0);
match self
.gateway_client
.send_sphinx_packet(mix_message.0, mix_message.1)
.await
{
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
debug_assert!(!mix_packets.is_empty());
let success = if mix_packets.len() == 1 {
let mix_packet = mix_packets.pop().unwrap();
self.gateway_client.send_mix_packet(mix_packet).await
} else {
self.gateway_client
.batch_send_mix_packets(mix_packets)
.await
};
match success {
Err(e) => {
error!("Failed to send sphinx packet to the gateway! - {:?}", e);
error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e);
self.consecutive_gateway_failure_count += 1;
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// todo: in the future this should initiate a 'graceful' shutdown
// todo: in the future this should initiate a 'graceful' shutdown or try
// to reconnect?
panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT)
}
}
Ok(_) => {
trace!("We *might* have managed to forward sphinx packet to the gateway!");
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
}
}
pub async fn run(&mut self) {
while let Some(mix_message) = self.mix_rx.next().await {
self.on_message(mix_message).await;
while let Some(mix_packets) = self.mix_rx.next().await {
self.on_messages(mix_packets).await;
}
}
@@ -13,10 +13,10 @@
// limitations under the License.
use super::PendingAcknowledgement;
use crate::client::real_messages_control::acknowledgement_control::ack_delay_queue::AckDelayQueue;
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
use log::*;
use nonexhaustive_delayqueue::NonExhaustiveDelayQueue;
use nymsphinx::chunking::fragment::FragmentIdentifier;
use nymsphinx::Delay as SphinxDelay;
use std::collections::HashMap;
@@ -105,7 +105,7 @@ pub(super) struct ActionController {
// previous version.
/// DelayQueue with all `PendingAcknowledgement` that are waiting to be either received or
/// retransmitted if their timer fires up.
pending_acks_timers: AckDelayQueue<FragmentIdentifier>,
pending_acks_timers: NonExhaustiveDelayQueue<FragmentIdentifier>,
/// Channel for receiving `Action`s from other modules.
incoming_actions: UnboundedReceiver<Action>,
@@ -124,7 +124,7 @@ impl ActionController {
ActionController {
config,
pending_acks_data: HashMap::new(),
pending_acks_timers: AckDelayQueue::new(),
pending_acks_timers: NonExhaustiveDelayQueue::new(),
incoming_actions: receiver,
retransmission_sender,
},
@@ -17,7 +17,7 @@ use super::PendingAcknowledgement;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::{InputMessage, InputMessageReceiver},
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use futures::StreamExt;
@@ -40,7 +40,7 @@ where
input_receiver: InputMessageReceiver,
message_preparer: MessagePreparer<R>,
action_sender: ActionSender,
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
}
@@ -55,7 +55,7 @@ where
input_receiver: InputMessageReceiver,
message_preparer: MessagePreparer<R>,
action_sender: ActionSender,
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
) -> Self {
@@ -85,12 +85,13 @@ where
match self
.message_preparer
.prepare_reply_for_use(data, reply_surb, topology, &self.ack_key)
.await
{
Ok((reply_id, sphinx_packet, first_hop)) => {
Ok((mix_packet, reply_id)) => {
// TODO: later probably write pending ack here
// and deal with them....
// ... somehow
Some(RealMessage::new(first_hop, sphinx_packet, reply_id))
Some(RealMessage::new(mix_packet, reply_id))
}
Err(err) => {
// TODO: should we have some mechanism to indicate to the user that the `reply_surb`
@@ -140,18 +141,18 @@ where
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient)
.await
.unwrap();
real_messages.push(RealMessage::new(
prepared_fragment.first_hop_address,
prepared_fragment.sphinx_packet,
prepared_fragment.mix_packet,
message_chunk.fragment_identifier(),
));
pending_acks.push(PendingAcknowledgement::new(
message_chunk,
prepared_fragment.total_delay,
recipient.clone(),
recipient,
));
}
@@ -183,11 +184,9 @@ where
};
// tells real message sender (with the poisson timer) to send this to the mix network
for real_message in real_messages {
self.real_message_sender
.unbounded_send(real_message)
.unwrap();
}
self.real_message_sender
.unbounded_send(real_messages)
.unwrap();
}
pub(super) async fn run(&mut self) {
@@ -18,12 +18,13 @@ use self::{
retransmission_request_listener::RetransmissionRequestListener,
sent_notification_listener::SentNotificationListener,
};
use super::real_traffic_stream::RealMessageSender;
use super::real_traffic_stream::BatchRealMessageSender;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::params::PacketMode;
use nymsphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -38,7 +39,6 @@ use std::{
};
use tokio::task::JoinHandle;
mod ack_delay_queue;
mod acknowledgement_listener;
mod action_controller;
mod input_message_listener;
@@ -87,7 +87,7 @@ impl PendingAcknowledgement {
pub(super) struct AcknowledgementControllerConnectors {
/// Channel used for forwarding prepared sphinx messages into the poisson sender
/// to be sent to the mix network.
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
/// Channel used for receiving raw messages from a client. The messages need to be put
/// into sphinx packets first.
@@ -104,7 +104,7 @@ pub(super) struct AcknowledgementControllerConnectors {
impl AcknowledgementControllerConnectors {
pub(super) fn new(
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
input_receiver: InputMessageReceiver,
sent_notifier: SentPacketNotificationReceiver,
ack_receiver: AcknowledgementReceiver,
@@ -131,6 +131,14 @@ pub(super) struct Config {
/// Average delay a data packet is going to get delayed at a single mixnode.
average_packet_delay: Duration,
/// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed
/// and keys reused.
packet_mode: PacketMode,
/// If the mode of the client is set to VPN it specifies number of packets created with the
/// same initial secret until it gets rotated.
vpn_key_reuse_limit: Option<usize>,
}
impl Config {
@@ -139,12 +147,16 @@ impl Config {
ack_wait_multiplier: f64,
average_ack_delay: Duration,
average_packet_delay: Duration,
packet_mode: PacketMode,
vpn_key_reuse_limit: Option<usize>,
) -> Self {
Config {
ack_wait_addition,
ack_wait_multiplier,
average_ack_delay,
average_packet_delay,
packet_mode,
vpn_key_reuse_limit,
}
}
}
@@ -182,9 +194,11 @@ where
let message_preparer = MessagePreparer::new(
rng,
ack_recipient.clone(),
ack_recipient,
config.average_packet_delay,
config.average_ack_delay,
config.packet_mode,
config.vpn_key_reuse_limit,
);
// will listen for any acks coming from the network
@@ -197,7 +211,7 @@ where
// will listen for any new messages from the client
let input_message_listener = InputMessageListener::new(
Arc::clone(&ack_key),
ack_recipient.clone(),
ack_recipient,
connectors.input_receiver,
message_preparer.clone(),
action_sender.clone(),
@@ -16,7 +16,7 @@ use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use super::RetransmissionRequestReceiver;
use crate::client::{
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use futures::StreamExt;
@@ -35,7 +35,7 @@ where
ack_recipient: Recipient,
message_preparer: MessagePreparer<R>,
action_sender: ActionSender,
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
request_receiver: RetransmissionRequestReceiver,
topology_access: TopologyAccessor,
}
@@ -49,7 +49,7 @@ where
ack_recipient: Recipient,
message_preparer: MessagePreparer<R>,
action_sender: ActionSender,
real_message_sender: RealMessageSender,
real_message_sender: BatchRealMessageSender,
request_receiver: RetransmissionRequestReceiver,
topology_access: TopologyAccessor,
) -> Self {
@@ -94,6 +94,7 @@ where
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient)
.await
.unwrap();
// if we have the ONLY strong reference to the ack data, it means it was removed from the
@@ -123,11 +124,10 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.real_message_sender
.unbounded_send(RealMessage::new(
prepared_fragment.first_hop_address,
prepared_fragment.sphinx_packet,
.unbounded_send(vec![RealMessage::new(
prepared_fragment.mix_packet,
frag_id,
))
)])
.unwrap();
}
@@ -22,7 +22,7 @@ use self::{
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::InputMessageReceiver, mix_traffic::MixMessageSender,
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
topology_control::TopologyAccessor,
};
use futures::channel::mpsc;
@@ -30,6 +30,7 @@ use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::PacketMode;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
@@ -41,13 +42,34 @@ mod real_traffic_stream;
// TODO: ack_key and self_recipient shouldn't really be part of this config
pub struct Config {
/// Key used to decrypt contents of received SURBAcks
ack_key: Arc<AckKey>,
ack_wait_multiplier: f64,
/// Given ack timeout in the form a * BASE_DELAY + b, it specifies the additive part `b`
ack_wait_addition: Duration,
/// Given ack timeout in the form a * BASE_DELAY + b, it specifies the multiplier `a`
ack_wait_multiplier: f64,
/// Address of `this` client.
self_recipient: Recipient,
average_packet_delay_duration: Duration,
average_ack_delay_duration: Duration,
/// Average delay between sending subsequent packets from this client.
average_message_sending_delay: Duration,
/// Average delay a data packet is going to get delayed at a single mixnode.
average_packet_delay_duration: Duration,
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
average_ack_delay_duration: Duration,
/// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed
/// and keys reused.
packet_mode: PacketMode,
/// If the mode of the client is set to VPN it specifies number of packets created with the
/// same initial secret until it gets rotated.
vpn_key_reuse_limit: Option<usize>,
}
impl Config {
@@ -59,6 +81,8 @@ impl Config {
average_message_sending_delay: Duration,
average_packet_delay_duration: Duration,
self_recipient: Recipient,
packet_mode: PacketMode,
vpn_key_reuse_limit: Option<usize>,
) -> Self {
Config {
ack_key,
@@ -68,6 +92,8 @@ impl Config {
average_message_sending_delay,
ack_wait_multiplier,
ack_wait_addition,
packet_mode,
vpn_key_reuse_limit,
}
}
}
@@ -87,7 +113,7 @@ impl RealMessagesController<OsRng> {
config: Config,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: MixMessageSender,
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
) -> Self {
@@ -108,6 +134,8 @@ impl RealMessagesController<OsRng> {
config.ack_wait_multiplier,
config.average_ack_delay_duration,
config.average_packet_delay_duration,
config.packet_mode,
config.vpn_key_reuse_limit,
);
let ack_control = AcknowledgementController::new(
@@ -115,7 +143,7 @@ impl RealMessagesController<OsRng> {
rng,
topology_access.clone(),
Arc::clone(&config.ack_key),
config.self_recipient.clone(),
config.self_recipient,
reply_key_storage,
ack_controller_connectors,
);
@@ -143,7 +171,7 @@ impl RealMessagesController<OsRng> {
}
}
pub(super) async fn run(&mut self) {
pub(super) async fn run(&mut self, vpn_mode: bool) {
let mut out_queue_control = self.out_queue_control.take().unwrap();
let mut ack_control = self.ack_control.take().unwrap();
@@ -151,7 +179,7 @@ impl RealMessagesController<OsRng> {
// the task to ever finish. This will of course change once we introduce
// graceful shutdowns.
let out_queue_control_fut = tokio::spawn(async move {
out_queue_control.run_out_queue_control().await;
out_queue_control.run_out_queue_control(vpn_mode).await;
error!("The out queue controller has finished execution!");
out_queue_control
});
@@ -170,9 +198,9 @@ impl RealMessagesController<OsRng> {
// &Handle is only passed for consistency sake with other client modules, but I think
// when we get to refactoring, we should apply gateway approach and make it implicit
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
pub fn start(mut self, handle: &Handle, vpn_mode: bool) -> JoinHandle<Self> {
handle.spawn(async move {
self.run().await;
self.run(vpn_mode).await;
self
})
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use futures::channel::mpsc;
@@ -20,12 +20,13 @@ use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::{clients::Recipient, nodes::NymNodeRoutingAddress};
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::chunking::fragment::FragmentIdentifier;
use nymsphinx::cover::generate_loop_cover_packet;
use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::utils::sample_poisson_duration;
use nymsphinx::SphinxPacket;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
@@ -76,11 +77,11 @@ where
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: MixMessageSender,
mix_tx: BatchMixMessageSender,
/// Channel used for receiving real, prepared, messages that must be first sufficiently delayed
/// before being sent out into the network.
real_receiver: RealMessageReceiver,
real_receiver: BatchRealMessageReceiver,
/// Represents full address of this client.
our_full_destination: Recipient,
@@ -90,23 +91,20 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
received_buffer: VecDeque<RealMessage>,
}
pub(crate) struct RealMessage {
first_hop_address: NymNodeRoutingAddress,
packet: SphinxPacket,
mix_packet: MixPacket,
fragment_id: FragmentIdentifier,
}
impl RealMessage {
pub(crate) fn new(
first_hop_address: NymNodeRoutingAddress,
packet: SphinxPacket,
fragment_id: FragmentIdentifier,
) -> Self {
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
RealMessage {
first_hop_address,
packet,
mix_packet,
fragment_id,
}
}
@@ -114,8 +112,8 @@ impl RealMessage {
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
// after sufficient delay
pub(crate) type RealMessageSender = mpsc::UnboundedSender<RealMessage>;
type RealMessageReceiver = mpsc::UnboundedReceiver<RealMessage>;
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
pub(crate) enum StreamMessage {
Cover,
@@ -145,14 +143,25 @@ where
let next = now + next_poisson_delay;
self.next_delay.reset(next);
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(real_available)));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there's an actual message - return it
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(
self.received_buffer.pop_front().unwrap(),
)))
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
@@ -168,8 +177,8 @@ where
config: Config,
ack_key: Arc<AckKey>,
sent_notifier: SentPacketNotificationSender,
mix_tx: MixMessageSender,
real_receiver: RealMessageReceiver,
mix_tx: BatchMixMessageSender,
real_receiver: BatchRealMessageReceiver,
rng: R,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
@@ -184,9 +193,18 @@ where
our_full_destination,
rng,
topology_access,
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
}
}
fn sent_notify(&self, frag_id: FragmentIdentifier) {
// well technically the message was not sent just yet, but now it's up to internal
// queues and client load rather than the required delay. So realistically we can treat
// whatever is about to happen as negligible additional delay.
trace!("{} is about to get sent to the mixnet", frag_id);
self.sent_notifier.unbounded_send(frag_id).unwrap();
}
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
@@ -209,7 +227,7 @@ where
}
let topology_ref = topology_ref_option.unwrap();
let cover_message = generate_loop_cover_packet(
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
@@ -217,22 +235,11 @@ where
self.config.average_ack_delay,
self.config.average_packet_delay,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
MixMessage::new(cover_message.0, cover_message.1)
.expect("Somehow failed to generate a loop cover message with a valid topology")
}
StreamMessage::Real(real_message) => {
// well technically the message was not sent just yet, but now it's up to internal
// queues and client load rather than the required delay. So realistically we can treat
// whatever is about to happen as negligible additional delay.
trace!(
"{} is about to get sent to the mixnet",
real_message.fragment_id
);
self.sent_notifier
.unbounded_send(real_message.fragment_id)
.unwrap();
MixMessage::new(real_message.first_hop_address, real_message.packet)
self.sent_notify(real_message.fragment_id);
real_message.mix_packet
}
};
@@ -240,7 +247,7 @@ where
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx.unbounded_send(next_message).unwrap();
self.mix_tx.unbounded_send(vec![next_message]).unwrap();
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
@@ -250,16 +257,42 @@ where
tokio::task::yield_now().await;
}
pub(crate) async fn run_out_queue_control(&mut self) {
async fn on_batch_received(&mut self, real_messages: Vec<RealMessage>) {
let mut mix_packets = Vec::with_capacity(real_messages.len());
for real_message in real_messages.into_iter() {
self.sent_notify(real_message.fragment_id);
mix_packets.push(real_message.mix_packet);
}
self.mix_tx.unbounded_send(mix_packets).unwrap();
}
// Send messages at certain rate and if no real traffic is available, send cover message.
async fn run_normal_out_queue(&mut self) {
// we should set initial delay only when we actually start the stream
self.next_delay = time::delay_for(sample_poisson_duration(
&mut self.rng,
self.config.average_message_sending_delay,
));
debug!("Starting out queue controller...");
while let Some(next_message) = self.next().await {
self.on_message(next_message).await;
}
}
// Send real message as soon as it's available and don't inject ANY cover traffic.
async fn run_vpn_out_queue(&mut self) {
while let Some(next_messages) = self.real_receiver.next().await {
self.on_batch_received(next_messages).await
}
}
pub(crate) async fn run_out_queue_control(&mut self, vpn_mode: bool) {
if vpn_mode {
debug!("Starting out queue controller in vpn mode...");
self.run_vpn_out_queue().await
} else {
debug!("Starting out queue controller...");
self.run_normal_out_queue().await
}
}
}
@@ -12,12 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::built_info;
use directory_client::DirectoryClient;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
use std::convert::TryInto;
use std::ops::Deref;
use std::sync::Arc;
use std::time;
@@ -147,7 +144,7 @@ impl TopologyRefresherConfig {
}
pub struct TopologyRefresher {
directory_client: directory_client::Client,
validator_client: validator_client::Client,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
}
@@ -157,25 +154,25 @@ impl TopologyRefresher {
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
) -> Self {
let directory_client_config = directory_client::Config::new(cfg.directory_server);
let directory_client = directory_client::Client::new(directory_client_config);
let validator_client_config = validator_client::Config::new(cfg.directory_server);
let validator_client = validator_client::Client::new(validator_client_config);
TopologyRefresher {
directory_client,
validator_client,
topology_accessor,
refresh_rate: cfg.refresh_rate,
}
}
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
match self.directory_client.get_topology().await {
match self.validator_client.get_active_topology().await {
Err(err) => {
error!("failed to get network topology! - {:?}", err);
error!("failed to get active network topology! - {:?}", err);
None
}
Ok(topology) => {
let nym_topology: NymTopology = topology.try_into().ok()?;
Some(nym_topology.filter_system_version(built_info::PKG_VERSION))
let nym_topology: NymTopology = topology.into();
Some(nym_topology.filter_system_version(env!("CARGO_PKG_VERSION")))
}
}
}
+205 -63
View File
@@ -13,27 +13,85 @@
// limitations under the License.
use config::NymConfig;
use serde::{Deserialize, Serialize};
use serde::{
de::{self, IntoDeserializer, Visitor},
Deserialize, Deserializer, Serialize,
};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::time;
use std::time::Duration;
pub mod persistence;
pub const MISSING_VALUE: &str = "MISSING VALUE";
// 'CLIENT'
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
pub const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = "http://testnet-validator1.nymtech.net:8081";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
// all delays are in milliseconds
const DEFAULT_ACK_WAIT_ADDITION: u64 = 800;
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000;
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 100;
const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 100;
const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000;
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000;
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500;
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_millis(30_000);
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000;
const ZERO_DELAY: Duration = Duration::from_nanos(0);
// custom function is defined to deserialize based on whether field contains a pre 0.9.0
// u64 interpreted as milliseconds or proper duration introduced in 0.9.0
//
// TODO: when we get to refactoring down the line, this code can just be removed
// and all Duration fields could just have #[serde(with = "humantime_serde")] instead
// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have,
// for argument sake, 0.11.0 out
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
struct DurationVisitor;
impl<'de> Visitor<'de> for DurationVisitor {
type Value = Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("u64 or a duration")
}
fn visit_i64<E>(self, value: i64) -> Result<Duration, E>
where
E: de::Error,
{
self.visit_u64(value as u64)
}
fn visit_u64<E>(self, value: u64) -> Result<Duration, E>
where
E: de::Error,
{
Ok(Duration::from_millis(Deserialize::deserialize(
value.into_deserializer(),
)?))
}
fn visit_str<E>(self, value: &str) -> Result<Duration, E>
where
E: de::Error,
{
humantime_serde::deserialize(value.into_deserializer())
}
}
deserializer.deserialize_any(DurationVisitor)
}
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
@@ -118,14 +176,26 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_listener = gateway_listener.into();
}
pub fn with_custom_directory<S: Into<String>>(&mut self, directory_server: S) {
self.client.directory_server = directory_server.into();
pub fn set_custom_validator<S: Into<String>>(&mut self, validator: S) {
self.client.validator_rest_url = validator.into();
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.average_packet_delay = 10;
self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s
self.debug.message_sending_average_delay = 5; // 200 "real" messages / s
self.debug.average_packet_delay = Duration::from_millis(1);
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2000000); // basically don't really send cover messages
self.debug.message_sending_average_delay = Duration::from_millis(4); // 250 "real" messages / s
}
pub fn set_vpn_mode(&mut self, vpn_mode: bool) {
self.client.vpn_mode = vpn_mode;
}
pub fn set_vpn_key_reuse_limit(&mut self, reuse_limit: usize) {
self.debug.vpn_key_reuse_limit = Some(reuse_limit)
}
pub fn set_custom_version(&mut self, version: &str) {
self.client.version = version.to_string();
}
pub fn get_id(&self) -> String {
@@ -164,8 +234,8 @@ impl<T: NymConfig> Config<T> {
self.client.ack_key_file.clone()
}
pub fn get_directory_server(&self) -> String {
self.client.directory_server.clone()
pub fn get_validator_rest_endpoint(&self) -> String {
self.client.validator_rest_url.clone()
}
pub fn get_gateway_id(&self) -> String {
@@ -177,40 +247,71 @@ impl<T: NymConfig> Config<T> {
}
// Debug getters
pub fn get_average_packet_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.average_packet_delay)
pub fn get_average_packet_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
self.debug.average_packet_delay
}
}
pub fn get_average_ack_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.average_ack_delay)
pub fn get_average_ack_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
self.debug.average_ack_delay
}
}
pub fn get_ack_wait_multiplier(&self) -> f64 {
self.debug.ack_wait_multiplier
}
pub fn get_ack_wait_addition(&self) -> time::Duration {
time::Duration::from_millis(self.debug.ack_wait_addition)
pub fn get_ack_wait_addition(&self) -> Duration {
self.debug.ack_wait_addition
}
pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay)
pub fn get_loop_cover_traffic_average_delay(&self) -> Duration {
self.debug.loop_cover_traffic_average_delay
}
pub fn get_message_sending_average_delay(&self) -> time::Duration {
time::Duration::from_millis(self.debug.message_sending_average_delay)
pub fn get_message_sending_average_delay(&self) -> Duration {
if self.client.vpn_mode {
ZERO_DELAY
} else {
self.debug.message_sending_average_delay
}
}
pub fn get_gateway_response_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.gateway_response_timeout)
pub fn get_gateway_response_timeout(&self) -> Duration {
self.debug.gateway_response_timeout
}
pub fn get_topology_refresh_rate(&self) -> time::Duration {
time::Duration::from_millis(self.debug.topology_refresh_rate)
pub fn get_topology_refresh_rate(&self) -> Duration {
self.debug.topology_refresh_rate
}
pub fn get_topology_resolution_timeout(&self) -> time::Duration {
time::Duration::from_millis(self.debug.topology_resolution_timeout)
pub fn get_topology_resolution_timeout(&self) -> Duration {
self.debug.topology_resolution_timeout
}
pub fn get_vpn_mode(&self) -> bool {
self.client.vpn_mode
}
pub fn get_vpn_key_reuse_limit(&self) -> Option<usize> {
match self.get_vpn_mode() {
false => None,
true => Some(
self.debug
.vpn_key_reuse_limit
.unwrap_or_else(|| DEFAULT_VPN_KEY_REUSE_LIMIT),
),
}
}
pub fn get_version(&self) -> &str {
&self.client.version
}
}
@@ -225,13 +326,23 @@ impl<T: NymConfig> Default for Config<T> {
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
/// ID specifies the human readable ID of this particular client.
id: String,
/// URL to the directory server.
directory_server: String,
/// URL to the validator server for obtaining network topology.
#[serde(default = "missing_string_value")]
validator_rest_url: String,
/// Special mode of the system such that all messages are sent as soon as they are received
/// and no cover traffic is generated. If set all message delays are set to 0 and overwriting
/// 'Debug' values will have no effect.
#[serde(default)]
vpn_mode: bool,
/// Path to file containing private identity key.
private_identity_key_file: PathBuf,
@@ -269,15 +380,17 @@ pub struct Client<T> {
nym_root_directory: PathBuf,
#[serde(skip)]
super_struct: PhantomData<T>,
super_struct: PhantomData<*const T>,
}
impl<T: NymConfig> Default for Client<T> {
fn default() -> Self {
// there must be explicit checks for whether id is not empty later
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
directory_server: DEFAULT_DIRECTORY_SERVER.to_string(),
validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(),
vpn_mode: false,
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_encryption_key_file: Default::default(),
@@ -295,31 +408,31 @@ impl<T: NymConfig> Default for Client<T> {
impl<T: NymConfig> Client<T> {
fn default_private_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_identity.pem")
T::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_identity.pem")
T::default_data_directory(id).join("public_identity.pem")
}
fn default_private_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("private_encryption.pem")
T::default_data_directory(id).join("private_encryption.pem")
}
fn default_public_encryption_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("public_encryption.pem")
T::default_data_directory(id).join("public_encryption.pem")
}
fn default_gateway_shared_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("gateway_shared.pem")
T::default_data_directory(id).join("gateway_shared.pem")
}
fn default_ack_key_file(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("ack_key.pem")
T::default_data_directory(id).join("ack_key.pem")
}
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store")
T::default_data_directory(id).join("reply_key_store")
}
}
@@ -340,15 +453,21 @@ pub struct Debug {
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
/// The provided value is interpreted as milliseconds.
average_packet_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
/// The provided value is interpreted as milliseconds.
average_ack_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
@@ -358,36 +477,58 @@ pub struct Debug {
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
/// The provided value is interpreted as milliseconds.
ack_wait_addition: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
ack_wait_addition: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
/// The provided value is interpreted as milliseconds.
loop_cover_traffic_average_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
loop_cover_traffic_average_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
/// The provided value is interpreted as milliseconds.
message_sending_average_delay: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
message_sending_average_delay: Duration,
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
/// The provided value is interpreted as milliseconds.
gateway_response_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
gateway_response_timeout: Duration,
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
/// The provided value is interpreted as milliseconds.
topology_refresh_rate: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
/// The provided value is interpreted as milliseconds.
topology_resolution_timeout: u64,
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "humantime_serde::serialize"
)]
topology_resolution_timeout: Duration,
/// If the mode of the client is set to VPN it specifies number of packets created with the
/// same initial secret until it gets rotated.
vpn_key_reuse_limit: Option<usize>,
}
impl Default for Debug {
@@ -402,6 +543,7 @@ impl Default for Debug {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
vpn_key_reuse_limit: None,
}
}
}
-102
View File
@@ -1,102 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub(crate) fn config_template() -> &'static str {
// 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 in mod.rs.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base client config options #####
[client]
# Human readable ID of this particular client.
id = '{{ client.id }}'
# URL to the directory server.
directory_server = '{{ client.directory_server }}'
# Path to file containing private identity key.
private_identity_key_file = '{{ client.private_identity_key_file }}'
# Path to file containing public identity key.
public_identity_key_file = '{{ client.public_identity_key_file }}'
# Path to file containing private encryption key.
private_encryption_key_file = '{{ client.private_encryption_key_file }}'
# Path to file containing public encryption key.
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# Full path to file containing reply encryption keys of all reply-SURBs we have ever
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_id }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_listener }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
gateway_shared_key_file = '{{ client.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.
ack_key_file = '{{ client.ack_key_file }}'
##### advanced configuration options #####
# Absolute path to the home Nym Clients directory.
nym_root_directory = '{{ client.nym_root_directory }}'
##### socket config options #####
[socket]
# allowed values are 'WebSocket' or 'None'
socket_type = '{{ socket.socket_type }}'
# if applicable (for the case of 'WebSocket'), the port on which the client
# will be listening for incoming requests
listening_port = {{ socket.listening_port }}
##### 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.
[debug]
average_packet_delay = {{ debug.average_packet_delay }}
average_ack_delay = {{ debug.average_ack_delay }}
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
message_sending_average_delay = {{ debug.message_sending_average_delay }}
"#
}
-1
View File
@@ -1,3 +1,2 @@
pub mod built_info;
pub mod client;
pub mod config;
+3 -7
View File
@@ -1,7 +1,6 @@
[package]
build = "build.rs"
name = "nym-client"
version = "0.8.1"
version = "0.9.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
@@ -34,17 +33,14 @@ tokio-tungstenite = "0.11.0" # websocket
client-core = { path = "../client-core" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
directory-client = { path = "../../common/client-libs/directory-client" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
[build-dependencies]
built = "0.4.3"
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
[dev-dependencies]
tempfile = "3.1.0"
serde_json = "1.0" # for the "textsend" example
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use built;
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}
@@ -3232,9 +3232,9 @@
}
},
"node-forge": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true
},
"node-libs-browser": {
@@ -4145,12 +4145,12 @@
"dev": true
},
"selfsigned": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
"version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true,
"requires": {
"node-forge": "0.9.0"
"node-forge": "^0.10.0"
}
},
"semver": {
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The file has been placed there by the build script.
include!(concat!(env!("OUT_DIR"), "/built.rs"));
+1 -24
View File
@@ -14,6 +14,7 @@
use crate::client::config::template::config_template;
use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
@@ -54,10 +55,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
@@ -135,23 +132,3 @@ impl Default for Socket {
}
}
}
#[cfg(test)]
mod client_config {
use super::*;
#[test]
fn after_saving_default_config_the_loaded_one_is_identical() {
// need to figure out how to do something similar but without touching the disk
// or the file system at all...
let temp_location = tempfile::tempdir().unwrap().path().join("config.toml");
let default_config = Config::new("foomp".to_string());
default_config
.save_to_file(Some(temp_location.clone()))
.unwrap();
let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap();
assert_eq!(default_config, loaded_config);
}
}
+14 -6
View File
@@ -24,11 +24,19 @@ pub(crate) fn config_template() -> &'static str {
##### main base client config options #####
[client]
# Version of the client for which this configuration was created.
version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# URL to the directory server.
directory_server = '{{ client.directory_server }}'
# URL to the validator server for obtaining network topology.
validator_rest_url = '{{ client.validator_rest_url }}'
# Special mode of the system such that all messages are sent as soon as they are received
# and no cover traffic is generated. If set all message delays are set to 0 and overwriting
# 'Debug' values will have no effect.
vpn_mode = {{ client.vpn_mode }}
# Path to file containing private identity key.
private_identity_key_file = '{{ client.private_identity_key_file }}'
@@ -93,10 +101,10 @@ listening_port = {{ socket.listening_port }}
[debug]
average_packet_delay = {{ debug.average_packet_delay }}
average_ack_delay = {{ debug.average_ack_delay }}
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
message_sending_average_delay = {{ debug.message_sending_average_delay }}
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
"#
}
+22 -9
View File
@@ -20,7 +20,7 @@ use client_core::client::inbound_messages::{
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
MixMessageReceiver, MixMessageSender, MixTrafficController,
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
@@ -43,6 +43,7 @@ use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySURB;
use nymsphinx::params::PacketMode;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
@@ -88,7 +89,7 @@ impl NymClient {
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
self.key_manager.encryption_keypair().public_key().clone(),
*self.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(self.config.get_base().get_gateway_id()).unwrap(),
@@ -100,7 +101,7 @@ impl NymClient {
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: MixMessageSender,
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
@@ -128,8 +129,14 @@ impl NymClient {
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: MixMessageSender,
mix_sender: BatchMixMessageSender,
) {
let packet_mode = if self.config.get_base().get_vpn_mode() {
PacketMode::VPN
} else {
PacketMode::Mix
};
let controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
@@ -138,6 +145,8 @@ impl NymClient {
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.as_mix_recipient(),
packet_mode,
self.config.get_base().get_vpn_key_reuse_limit(),
);
info!("Starting real traffic stream...");
@@ -154,7 +163,8 @@ impl NymClient {
reply_key_storage,
)
});
real_messages_controller.start(self.runtime.handle());
real_messages_controller
.start(self.runtime.handle(), self.config.get_base().get_vpn_mode());
}
// buffer controlling all messages fetched from provider
@@ -216,7 +226,7 @@ impl NymClient {
// the current global view of topology
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_directory_server(),
self.config.get_base().get_validator_rest_endpoint(),
self.config.get_base().get_topology_refresh_rate(),
);
let mut topology_refresher =
@@ -225,7 +235,7 @@ impl NymClient {
// components depending on topology would see a non-empty view
info!(
"Obtaining initial network topology from {}",
self.config.get_base().get_directory_server()
self.config.get_base().get_validator_rest_endpoint()
);
self.runtime.block_on(topology_refresher.refresh());
@@ -250,7 +260,7 @@ impl NymClient {
// requests?
fn start_mix_traffic_controller(
&mut self,
mix_rx: MixMessageReceiver,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
@@ -378,7 +388,10 @@ impl NymClient {
input_receiver,
sphinx_message_sender.clone(),
);
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
if !self.config.get_base().get_vpn_mode() {
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
}
match self.config.get_socket_type() {
SocketType::WebSocket => {
+76 -53
View File
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::built_info;
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
@@ -20,21 +19,14 @@ use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use config::NymConfig;
use crypto::asymmetric::identity;
use directory_client::DirectoryClient;
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use rand::rngs::OsRng;
use rand::seq::SliceRandom;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, NymTopology};
const GOOD_GATEWAYS: [&str; 2] = [
"D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr",
"5nrYxPR8gt2Gzo2BbHtsGf66KAEQY91WmM1eW78EphNy",
];
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
.about("Initialise a Nym client. Do this first!")
@@ -49,9 +41,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the client is getting topology from")
.arg(Arg::with_name("validator")
.long("validator")
.help("Address of the validator server the client is getting topology from")
.takes_value(true),
)
.arg(Arg::with_name("disable-socket")
@@ -64,6 +56,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket (if applicable) to listen on in all subsequent runs")
.takes_value(true)
)
.arg(Arg::with_name("vpn-mode")
.long("vpn-mode")
.help("Set the vpn mode of the client")
.long_help(
r#"
Special mode of the system such that all messages are sent as soon as they are received
and no cover traffic is generated. If set all message delays are set to 0 and overwriting
'Debug' values will have no effect.
"#
)
)
.arg(Arg::with_name("fastmode")
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
@@ -92,28 +95,30 @@ async fn register_with_gateway(
.expect("failed to register with the gateway!")
}
async fn gateway_details(directory_server: &str, gateway_id: &str) -> gateway::Node {
let directory_client_config = directory_client::Config::new(directory_server.to_string());
let directory_client = directory_client::Client::new(directory_client_config);
let topology = directory_client.get_topology().await.unwrap();
let nym_topology: NymTopology = topology.try_into().expect("Invalid topology data!");
let version_filtered_topology = nym_topology.filter_system_version(built_info::PKG_VERSION);
async fn gateway_details(validator_server: &str, chosen_gateway_id: Option<&str>) -> gateway::Node {
let validator_client_config = validator_client::Config::new(validator_server.to_string());
let validator_client = validator_client::Client::new(validator_client_config);
let topology = validator_client.get_active_topology().await.unwrap();
let nym_topology: NymTopology = topology.into();
let version_filtered_topology = nym_topology.filter_system_version(env!("CARGO_PKG_VERSION"));
version_filtered_topology
.gateways()
.iter()
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
.expect(&*format!("no gateway with id {} exists!", gateway_id))
.clone()
}
// if we have chosen particular gateway - use it, otherwise choose a random one.
// (remember that in active topology all gateways have at least 100 reputation so should
// be working correctly)
fn select_gateway(arg: Option<&str>) -> &str {
if let Some(gateway_id) = arg {
gateway_id
if let Some(gateway_id) = chosen_gateway_id {
version_filtered_topology
.gateways()
.iter()
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
.expect(&*format!("no gateway with id {} exists!", gateway_id))
.clone()
} else {
// TODO1: this should only be done on testnet
// TODO2: it should probably check if chosen gateway is actually online
GOOD_GATEWAYS.choose(&mut rand::thread_rng()).unwrap()
version_filtered_topology
.gateways()
.choose(&mut rand::thread_rng())
.expect("there are no gateways on the network!")
.clone()
}
}
@@ -121,7 +126,16 @@ pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
let already_init = if Config::default_config_file_path(id).exists() {
println!("Client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let mut config = Config::new(id);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
@@ -131,33 +145,42 @@ pub fn execute(matches: &ArgMatches) {
config.get_base_mut().set_high_default_traffic_volume();
}
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
// if client was already initialised, don't generate new keys, not re-register with gateway
// (because this would create new shared key)
if !already_init {
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
let gateway_id = select_gateway(matches.value_of("gateway"));
config.get_base_mut().with_gateway_id(gateway_id);
let chosen_gateway_id = matches.value_of("gateway");
let registration_fut = async {
let gate_details =
gateway_details(&config.get_base().get_directory_server(), gateway_id).await;
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.client_listener)
};
let registration_fut = async {
let gate_details = gateway_details(
&config.get_base().get_validator_rest_endpoint(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.client_listener)
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let mut rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let mut rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
}
let config_save_location = config.get_config_file_save_location();
config
+9 -4
View File
@@ -15,12 +15,13 @@
use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(directory) = matches.value_of("directory") {
config.get_base_mut().with_custom_directory(directory);
if let Some(validator) = matches.value_of("validator") {
config.get_base_mut().set_custom_validator(validator);
}
if let Some(gateway_id) = matches.value_of("gateway") {
@@ -31,6 +32,10 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
config = config.with_socket(SocketType::None);
}
if matches.is_present("vpn-mode") {
config.get_base_mut().set_vpn_mode(true);
}
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
if let Err(err) = port {
// if port was overridden, it must be parsable
+47 -11
View File
@@ -17,6 +17,8 @@ use crate::client::NymClient;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("run")
@@ -28,14 +30,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.required(true)
)
// the rest of arguments are optional, they are used to override settings in config file
.arg(Arg::with_name("config")
.long("config")
.help("Custom path to the nym-mixnet-client configuration file")
.takes_value(true)
)
.arg(Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the client is getting topology from")
.arg(Arg::with_name("validator")
.long("validator")
.help("Address of the validator server the client is getting topology from")
.takes_value(true),
)
.arg(Arg::with_name("gateway")
@@ -47,6 +44,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("disable-socket")
.help("Whether to not start the websocket")
)
.arg(Arg::with_name("vpn-mode")
.long("vpn-mode")
.help("Set the vpn mode of the client")
.long_help(
r#"
Special mode of the system such that all messages are sent as soon as they are received
and no cover traffic is generated. If set all message delays are set to 0 and overwriting
'Debug' values will have no effect.
"#
)
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
@@ -55,14 +63,42 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
)
}
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", 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");
true
} else {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config =
Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id))
.expect("Failed to load config file");
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: {})", id, err);
return;
}
};
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever();
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use client_core::config::DEFAULT_VALIDATOR_REST_ENDPOINT;
use config::NymConfig;
use std::fmt::Display;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade mixnode 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 pre_090_upgrade(from: &str, mut config: Config) -> Config {
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
process::exit(1)
}
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE {
eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade.");
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
}
print_start_upgrade(&from_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
println!(
"Setting validator REST endpoint to to {}",
DEFAULT_VALIDATOR_REST_ENDPOINT
);
config
.get_base_mut()
.set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT);
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the mixnode")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-client we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// 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 current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_base().get_version() == MISSING_VALUE {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version =
Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
-1
View File
@@ -12,6 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod built_info;
pub mod client;
pub mod websocket;
+4 -3
View File
@@ -14,7 +14,6 @@
use clap::{App, ArgMatches};
pub mod built_info;
pub mod client;
pub mod commands;
pub mod websocket;
@@ -25,11 +24,12 @@ fn main() {
println!("{}", banner());
let arg_matches = App::new("Nym Client")
.version(built_info::PKG_VERSION)
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.about("Implementation of the Nym Client")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
@@ -60,7 +61,7 @@ fn banner() -> String {
(client - version {:})
"#,
built_info::PKG_VERSION
env!("CARGO_PKG_VERSION")
)
}
+2 -2
View File
@@ -57,7 +57,7 @@ impl Clone for Handler {
Handler {
msg_input: self.msg_input.clone(),
buffer_requester: self.buffer_requester.clone(),
self_full_address: self.self_full_address.clone(),
self_full_address: self.self_full_address,
socket: None,
received_response_type: Default::default(),
}
@@ -112,7 +112,7 @@ impl Handler {
}
fn handle_self_address(&self) -> ServerResponse {
ServerResponse::SelfAddress(self.self_full_address.clone())
ServerResponse::SelfAddress(self.self_full_address)
}
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
+1 -4
View File
@@ -28,10 +28,7 @@ enum State {
impl State {
fn is_connected(&self) -> bool {
match self {
State::Connected => true,
_ => false,
}
matches!(self, State::Connected)
}
}
@@ -288,7 +288,7 @@ mod tests {
let recipient_string = recipient.to_string();
let send_request_no_surb = ClientRequest::Send {
recipient: recipient.clone(),
recipient,
message: b"foomp".to_vec(),
with_reply_surb: false,
};
@@ -310,12 +310,10 @@ impl ServerResponse {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => {
return Err(error::Error::new(
ErrorKind::UnknownResponse,
format!("type {}", n),
))
}
n => Err(error::Error::new(
ErrorKind::UnknownResponse,
format!("type {}", n),
)),
}
}
+3 -9
View File
@@ -1,7 +1,6 @@
[package]
build = "build.rs"
name = "nym-socks5-client"
version = "0.8.1"
version = "0.9.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
@@ -26,7 +25,6 @@ tokio = { version = "0.2", features = ["rt-threaded"] }
client-core = { path = "../client-core" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
directory-client = { path = "../../common/client-libs/directory-client" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" }
@@ -34,9 +32,5 @@ ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
[build-dependencies]
built = "0.4.3"
[dev-dependencies]
tempfile = "3.1.0"
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use built;
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The file has been placed there by the build script.
include!(concat!(env!("OUT_DIR"), "/built.rs"));
+1 -25
View File
@@ -14,6 +14,7 @@
use crate::client::config::template::config_template;
use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
use config::NymConfig;
use nymsphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
@@ -37,10 +38,6 @@ impl NymConfig for Config {
config_template()
}
fn config_file_name() -> String {
"config.toml".to_string()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
@@ -131,24 +128,3 @@ impl Default for Socks5 {
}
}
}
#[cfg(test)]
mod client_config {
use super::*;
#[test]
fn after_saving_default_config_the_loaded_one_is_identical() {
// need to figure out how to do something similar but without touching the disk
// or the file system at all...
let temp_location = tempfile::tempdir().unwrap().path().join("config.toml");
let fake_address = "CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML";
let default_config = Config::new("foomp", fake_address);
default_config
.save_to_file(Some(temp_location.clone()))
.unwrap();
let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap();
assert_eq!(default_config, loaded_config);
}
}
+14 -6
View File
@@ -24,11 +24,19 @@ pub(crate) fn config_template() -> &'static str {
##### main base client config options #####
[client]
# Version of the client for which this configuration was created.
version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# URL to the directory server.
directory_server = '{{ client.directory_server }}'
# URL to the validator server for obtaining network topology.
validator_rest_url = '{{ client.validator_rest_url }}'
# Special mode of the system such that all messages are sent as soon as they are received
# and no cover traffic is generated. If set all message delays are set to 0 and overwriting
# 'Debug' values will have no effect.
vpn_mode = {{ client.vpn_mode }}
# Path to file containing private identity key.
private_identity_key_file = '{{ client.private_identity_key_file }}'
@@ -92,10 +100,10 @@ listening_port = {{ socks5.listening_port }}
[debug]
average_packet_delay = {{ debug.average_packet_delay }}
average_ack_delay = {{ debug.average_ack_delay }}
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
message_sending_average_delay = {{ debug.message_sending_average_delay }}
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
"#
}
+21 -9
View File
@@ -23,7 +23,7 @@ use client_core::client::inbound_messages::{
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
MixMessageReceiver, MixMessageSender, MixTrafficController,
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
@@ -43,6 +43,7 @@ use gateway_client::{
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::params::PacketMode;
use tokio::runtime::Runtime;
pub(crate) mod config;
@@ -76,7 +77,7 @@ impl NymClient {
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
self.key_manager.encryption_keypair().public_key().clone(),
*self.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(self.config.get_base().get_gateway_id()).unwrap(),
@@ -88,7 +89,7 @@ impl NymClient {
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: MixMessageSender,
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
@@ -116,8 +117,14 @@ impl NymClient {
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: MixMessageSender,
mix_sender: BatchMixMessageSender,
) {
let packet_mode = if self.config.get_base().get_vpn_mode() {
PacketMode::VPN
} else {
PacketMode::Mix
};
let controller_config = client_core::client::real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
@@ -126,6 +133,8 @@ impl NymClient {
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.as_mix_recipient(),
packet_mode,
self.config.get_base().get_vpn_key_reuse_limit(),
);
info!("Starting real traffic stream...");
@@ -142,7 +151,8 @@ impl NymClient {
reply_key_storage,
)
});
real_messages_controller.start(self.runtime.handle());
real_messages_controller
.start(self.runtime.handle(), self.config.get_base().get_vpn_mode());
}
// buffer controlling all messages fetched from provider
@@ -204,7 +214,7 @@ impl NymClient {
// the current global view of topology
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_directory_server(),
self.config.get_base().get_validator_rest_endpoint(),
self.config.get_base().get_topology_refresh_rate(),
);
let mut topology_refresher =
@@ -213,7 +223,7 @@ impl NymClient {
// components depending on topology would see a non-empty view
info!(
"Obtaining initial network topology from {}",
self.config.get_base().get_directory_server()
self.config.get_base().get_validator_rest_endpoint()
);
self.runtime.block_on(topology_refresher.refresh());
@@ -238,7 +248,7 @@ impl NymClient {
// requests?
fn start_mix_traffic_controller(
&mut self,
mix_rx: MixMessageReceiver,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
@@ -330,7 +340,9 @@ impl NymClient {
input_receiver,
sphinx_message_sender.clone(),
);
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
if !self.config.get_base().get_vpn_mode() {
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
}
self.start_socks5_listener(received_buffer_request_sender, input_sender);
info!("Client startup finished!");
+75 -53
View File
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::built_info;
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
@@ -20,20 +19,13 @@ use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use config::NymConfig;
use crypto::asymmetric::identity;
use directory_client::DirectoryClient;
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use rand::{prelude::SliceRandom, rngs::OsRng};
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, NymTopology};
const GOOD_GATEWAYS: [&str; 2] = [
"D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr",
"5nrYxPR8gt2Gzo2BbHtsGf66KAEQY91WmM1eW78EphNy",
];
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
.about("Initialise a Nym client. Do this first!")
@@ -54,9 +46,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the client is getting topology from")
.arg(Arg::with_name("validator")
.long("validator")
.help("Address of the validator server the client is getting topology from")
.takes_value(true),
)
.arg(Arg::with_name("port")
@@ -65,6 +57,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket to listen on in all subsequent runs")
.takes_value(true)
)
.arg(Arg::with_name("vpn-mode")
.long("vpn-mode")
.help("Set the vpn mode of the client")
.long_help(
r#"
Special mode of the system such that all messages are sent as soon as they are received
and no cover traffic is generated. If set all message delays are set to 0 and overwriting
'Debug' values will have no effect.
"#
)
)
.arg(Arg::with_name("fastmode")
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
@@ -93,28 +96,30 @@ async fn register_with_gateway(
.expect("failed to register with the gateway!")
}
async fn gateway_details(directory_server: &str, gateway_id: &str) -> gateway::Node {
let directory_client_config = directory_client::Config::new(directory_server.to_string());
let directory_client = directory_client::Client::new(directory_client_config);
let topology = directory_client.get_topology().await.unwrap();
let nym_topology: NymTopology = topology.try_into().expect("Invalid topology data!");
let version_filtered_topology = nym_topology.filter_system_version(built_info::PKG_VERSION);
async fn gateway_details(validator_server: &str, chosen_gateway_id: Option<&str>) -> gateway::Node {
let validator_client_config = validator_client::Config::new(validator_server.to_string());
let validator_client = validator_client::Client::new(validator_client_config);
let topology = validator_client.get_active_topology().await.unwrap();
let nym_topology: NymTopology = topology.into();
let version_filtered_topology = nym_topology.filter_system_version(env!("CARGO_PKG_VERSION"));
version_filtered_topology
.gateways()
.iter()
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
.expect(&*format!("no gateway with id {} exists!", gateway_id))
.clone()
}
// if we have chosen particular gateway - use it, otherwise choose a random one.
// (remember that in active topology all gateways have at least 100 reputation so should
// be working correctly)
fn select_gateway(arg: Option<&str>) -> &str {
if let Some(gateway_id) = arg {
gateway_id
if let Some(gateway_id) = chosen_gateway_id {
version_filtered_topology
.gateways()
.iter()
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
.expect(&*format!("no gateway with id {} exists!", gateway_id))
.clone()
} else {
// TODO1: this should only be done on testnet
// TODO2: it should probably check if chosen gateway is actually online
GOOD_GATEWAYS.choose(&mut rand::thread_rng()).unwrap()
version_filtered_topology
.gateways()
.choose(&mut rand::thread_rng())
.expect("there are no gateways on the network!")
.clone()
}
}
@@ -124,7 +129,15 @@ pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap(); // required for now
let provider_address = matches.value_of("provider").unwrap();
let already_init = if Config::default_config_file_path(id).exists() {
println!("Socks5 client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let mut config = Config::new(id, provider_address);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
@@ -134,33 +147,42 @@ pub fn execute(matches: &ArgMatches) {
config.get_base_mut().set_high_default_traffic_volume();
}
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
// if client was already initialised, don't generate new keys, not re-register with gateway
// (because this would create new shared key)
if !already_init {
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
let gateway_id = select_gateway(matches.value_of("gateway"));
config.get_base_mut().with_gateway_id(gateway_id);
let chosen_gateway_id = matches.value_of("gateway");
let registration_fut = async {
let gate_details =
gateway_details(&config.get_base().get_directory_server(), gateway_id).await;
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.client_listener)
};
let registration_fut = async {
let gate_details = gateway_details(
&config.get_base().get_validator_rest_endpoint(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.client_listener)
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let mut rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let mut rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
}
let config_save_location = config.get_config_file_save_location();
config
+9 -4
View File
@@ -15,18 +15,23 @@
use crate::client::config::Config;
use clap::ArgMatches;
pub mod init;
pub mod run;
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(directory) = matches.value_of("directory") {
config.get_base_mut().with_custom_directory(directory);
if let Some(validator) = matches.value_of("validator") {
config.get_base_mut().set_custom_validator(validator);
}
if let Some(gateway_id) = matches.value_of("gateway") {
config.get_base_mut().with_gateway_id(gateway_id);
}
if matches.is_present("vpn-mode") {
config.get_base_mut().set_vpn_mode(true);
}
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
if let Err(err) = port {
// if port was overridden, it must be parsable
+47 -6
View File
@@ -17,6 +17,8 @@ use crate::client::NymClient;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("run")
@@ -38,9 +40,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Address of the socks5 provider to send messages to.")
.takes_value(true)
)
.arg(Arg::with_name("directory")
.long("directory")
.help("Address of the directory server the client is getting topology from")
.arg(Arg::with_name("validator")
.long("validator")
.help("Address of the validator server the client is getting topology from")
.takes_value(true),
)
.arg(Arg::with_name("gateway")
@@ -48,6 +50,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
.takes_value(true)
)
.arg(Arg::with_name("vpn-mode")
.long("vpn-mode")
.help("Set the vpn mode of the client")
.long_help(
r#"
Special mode of the system such that all messages are sent as soon as they are received
and no cover traffic is generated. If set all message delays are set to 0 and overwriting
'Debug' values will have no effect.
"#
)
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
@@ -56,14 +69,42 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
)
}
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", 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");
true
} else {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config =
Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id))
.expect("Failed to load config file");
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: {})", id, err);
return;
}
};
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever();
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use client_core::config::DEFAULT_VALIDATOR_REST_ENDPOINT;
use config::NymConfig;
use std::fmt::Display;
use std::process;
use version_checker::{parse_version, Version};
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade mixnode 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 pre_090_upgrade(from: &str, mut config: Config) -> Config {
// this is not extracted to separate function as you only have to manually pass version
// if upgrading from pre090 version
let from = match from.strip_prefix("v") {
Some(stripped) => stripped,
None => from,
};
let from = match from.strip_prefix("V") {
Some(stripped) => stripped,
None => from,
};
let from_version = parse_version(from).expect("invalid version provided!");
if from_version.major == 0 && from_version.minor < 8 {
// technically this could be implemented, but is there any point in that?
eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead");
process::exit(1)
}
if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 {
eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version");
process::exit(1)
}
// note: current is guaranteed to not have any `build` information suffix (nor pre-release
// information), as this was asserted at the beginning of this command)
//
// upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate
// step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0)
// this way we don't need to have all the crazy paths on how to upgrade from any version to any
// other version. We just upgrade one minor version at a time.
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let to_version = if current.major == 0 && current.minor == 9 {
current
} else {
Version::new(0, 9, 0)
};
if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE {
eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade.");
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
}
print_start_upgrade(&from_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
println!(
"Setting validator REST endpoint to to {}",
DEFAULT_VALIDATOR_REST_ENDPOINT
);
config
.get_base_mut()
.set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT);
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&from_version, &to_version);
process::exit(1);
});
print_successful_upgrade(from_version, to_version);
config
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the mixnode")
.arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-socks5-client we want to upgrade")
.takes_value(true)
.required(true),
)
// the rest of arguments depend on the upgrade path
.arg(Arg::with_name("current version")
.long("current-version")
.help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'")
.takes_value(true)
)
}
pub fn execute(matches: &ArgMatches) {
let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
// technically this is not a correct way of checking it as a released version might contain valid build identifiers
// 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 current.is_prerelease() || !current.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
current
);
process::exit(1)
}
let id = matches.value_of("id").unwrap();
let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
process::exit(1)
});
// versions fields were added in 0.9.0
if existing_config.get_base().get_version() == MISSING_VALUE {
let self_reported_version = matches.value_of("current version").unwrap_or_else(|| {
eprintln!(
"trying to upgrade from pre v0.9.0 without providing current system version!"
);
process::exit(1)
});
// upgrades up to 0.9.0
existing_config = pre_090_upgrade(self_reported_version, existing_config);
}
let config_version =
Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| {
eprintln!("failed to parse node version! - {:?}", err);
process::exit(1)
});
if config_version.is_prerelease() || !config_version.build.is_empty() {
eprintln!(
"Trying to upgrade to from non-released version {}. This is not supported!",
current
);
process::exit(1)
}
// here be upgrade path to 0.10.0 and beyond based on version number from config
if config_version == current {
println!("You're using the most recent version!");
} else {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current);
process::exit(1)
}
}
-1
View File
@@ -11,6 +11,5 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod built_info;
pub mod client;
pub mod socks;
+4 -3
View File
@@ -14,7 +14,6 @@
use clap::{App, ArgMatches};
pub mod built_info;
pub mod client;
mod commands;
pub mod socks;
@@ -25,11 +24,12 @@ fn main() {
println!("{}", banner());
let arg_matches = App::new("Nym Socks5 Proxy")
.version(built_info::PKG_VERSION)
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
@@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
@@ -60,7 +61,7 @@ fn banner() -> String {
(socks5 proxy - version {:})
"#,
built_info::PKG_VERSION
env!("CARGO_PKG_VERSION")
)
}
+35 -66
View File
@@ -10,15 +10,13 @@ use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use ordered_buffer::{OrderedMessageBuffer, OrderedMessageSender};
use pin_project::pin_project;
use proxy_helpers::available_reader::AvailableReader;
use proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use socks5_requests::{ConnectionId, Request};
use socks5_requests::{ConnectionId, RemoteAddress, Request};
use std::net::{Shutdown, SocketAddr};
use std::pin::Pin;
use tokio::prelude::*;
@@ -145,8 +143,7 @@ pub(crate) struct SocksClient {
impl Drop for SocksClient {
fn drop(&mut self) {
// TODO: decrease to debug/trace
info!("socksclient is going out of scope - the stream is getting dropped!");
info!("Connection {} is getting closed", self.connection_id);
debug!("Connection {} is getting closed", self.connection_id);
self.controller_sender
.unbounded_send(ControllerCommand::Remove(self.connection_id))
.unwrap();
@@ -222,26 +219,38 @@ impl SocksClient {
Ok(())
}
async fn send_request_to_mixnet(&mut self, request: Request) {
self.send_to_mixnet(request.into_bytes()).await;
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(
self.connection_id,
remote_address.clone(),
self.self_address.clone(),
);
let input_message =
InputMessage::new_fresh(self.service_provider.clone(), req.into_bytes(), false);
self.input_sender.unbounded_send(input_message).unwrap();
}
async fn run_proxy(
&mut self,
conn_receiver: ConnectionReceiver,
message_sender: OrderedMessageSender,
) {
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
self.send_connect_to_mixnet(remote_proxy_target.clone())
.await;
let stream = self.stream.run_proxy();
let local_stream_remote = stream
.peer_addr()
.expect("failed to extract peer address")
.to_string();
let connection_id = self.connection_id;
let input_sender = self.input_sender.clone();
let recipient = self.service_provider.clone();
let recipient = self.service_provider;
let (stream, _) = ProxyRunner::new(
stream,
local_stream_remote,
remote_proxy_target,
conn_receiver,
input_sender,
connection_id,
message_sender,
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
@@ -262,14 +271,9 @@ impl SocksClient {
// setup for receiving from the mixnet
let (mix_sender, mix_receiver) = mpsc::unbounded();
let ordered_buffer = OrderedMessageBuffer::new();
self.controller_sender
.unbounded_send(ControllerCommand::Insert(
self.connection_id,
mix_sender,
ordered_buffer,
))
.unbounded_send(ControllerCommand::Insert(self.connection_id, mix_sender))
.unwrap();
match request.command {
@@ -278,26 +282,16 @@ impl SocksClient {
trace!("Connecting to: {:?}", remote_address.clone());
self.acknowledge_socks5().await;
let mut message_sender = OrderedMessageSender::new();
// 'connect' needs to be handled manually due to different structure,
// but still needs to have correct sequence number on it!
// read whatever we can
let available_reader = AvailableReader::new(&mut self.stream);
let (request_data_bytes, _) = available_reader.await?;
let ordered_message = message_sender.wrap_message(request_data_bytes.to_vec());
let socks_provider_request = Request::new_connect(
self.connection_id,
info!(
"Starting proxy for {} (id: {})",
remote_address.clone(),
ordered_message,
self.self_address.clone(),
self.connection_id
);
self.run_proxy(mix_receiver, remote_address.clone()).await;
info!(
"Proxy for {} is finished (id: {})",
remote_address, self.connection_id
);
self.send_request_to_mixnet(socks_provider_request).await;
info!("Starting proxy for {}", remote_address.clone());
self.run_proxy(mix_receiver, message_sender).await;
info!("Proxy for {} is finished", remote_address);
}
SocksCommand::Bind => unimplemented!(), // not handled
@@ -307,16 +301,6 @@ impl SocksClient {
Ok(())
}
/// Send serialized Socks5 request bytes to the mixnet. The request stream
/// will be chunked up into a series of one or more Sphinx packets and
/// reassembled at the destination service provider at the other end, then
/// sent onwards anonymously.
async fn send_to_mixnet(&self, request_bytes: Vec<u8>) {
let input_message =
InputMessage::new_fresh(self.service_provider.clone(), request_bytes, false);
self.input_sender.unbounded_send(input_message).unwrap();
}
/// Writes a Socks5 header back to the requesting client's TCP stream,
/// basically saying "I acknowledge your request and am dealing with it".
async fn acknowledge_socks5(&mut self) {
@@ -381,27 +365,12 @@ impl SocksClient {
// Username parsing
let ulen = header[1];
let mut username = Vec::with_capacity(ulen as usize);
// For some reason the vector needs to actually be full
for _ in 0..ulen {
username.push(0);
}
let mut username = vec![0; ulen as usize];
self.stream.read_exact(&mut username).await?;
// Password Parsing
let mut plen = [0u8; 1];
self.stream.read_exact(&mut plen).await?;
let mut password = Vec::with_capacity(plen[0] as usize);
// For some reason the vector needs to actually be full
for _ in 0..plen[0] {
password.push(0);
}
let plen = self.stream.read_u8().await?;
let mut password = vec![0; plen as usize];
self.stream.read_exact(&mut password).await?;
let username_str = String::from_utf8(username)?;
+5 -2
View File
@@ -1,6 +1,7 @@
use super::types::{AddrType, ResponseCode, SocksProxyError};
use super::{utils as socks_utils, SOCKS_VERSION};
use log::*;
use std::fmt::{self, Display};
use tokio::prelude::*;
/// A Socks5 request hitting the proxy.
@@ -95,12 +96,14 @@ impl SocksRequest {
port,
})
}
}
impl Display for SocksRequest {
/// Print out the address and port to a String.
/// This might return domain:port, ipv6:port, or ipv4:port.
pub(crate) fn to_string(&self) -> String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
format!("{}:{}", address, self.port)
write!(f, "{}:{}", address, self.port)
}
}
+2 -2
View File
@@ -72,9 +72,9 @@ impl SphinxSocksServer {
stream,
self.authenticator.clone(),
input_sender.clone(),
self.service_provider.clone(),
self.service_provider,
controller_sender.clone(),
self.self_address.clone(),
self.self_address,
);
tokio::spawn(async move {
+3 -8
View File
@@ -1,8 +1,7 @@
[package]
build = "build.rs"
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "0.8.1"
version = "0.9.0"
edition = "2018"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -28,8 +27,8 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
directory-client = { path = "../../common/client-libs/directory-client" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
validator-client = { path = "../../common/client-libs/validator-client" }
wasm-utils = { path = "../../common/wasm-utils" }
# The `console_error_panic_hook` crate provides better debugging of panics by
@@ -45,14 +44,10 @@ console_error_panic_hook = { version = "0.1", optional = true }
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4", optional = true }
[build-dependencies]
built = "0.4.3"
[dev-dependencies]
wasm-bindgen-test = "0.2"
[package.metadata.wasm-pack.profile.release]
# this needs to be disabled until https://github.com/rustwasm/wasm-pack/issues/886 is resolved
wasm-opt = ["-Oz", "--enable-mutable-globals"]
wasm-opt = false
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use built;
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}
+3 -3
View File
@@ -24,10 +24,10 @@ async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// directory server we will use to get topology from
const directory = "https://qa-directory.nymtech.net"; //"http://localhost:8080";
// validator server we will use to get topology from
const validator = "http://testnet-validator1.nymtech.net:8081"; //"http://localhost:8081";
client = new NymClient(directory);
client = new NymClient(validator);
const on_message = (msg) => displayReceived(msg);
const on_connect = () => console.log("Established (and authenticated) gateway connection!");
+205 -202
View File
@@ -358,9 +358,10 @@
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"dev": true,
"requires": {
"bn.js": "4.11.8",
"bn.js": "4.11.9",
"inherits": "2.0.4",
"minimalistic-assert": "1.0.1"
"minimalistic-assert": "1.0.1",
"safer-buffer": "2.1.2"
},
"dependencies": {
"bn.js": {
@@ -410,7 +411,7 @@
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "4.17.15"
"lodash": "4.17.20"
}
},
"async-each": {
@@ -662,44 +663,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
},
"browserify-sign": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
"dev": true,
"requires": {
"bn.js": "5.1.1",
"browserify-rsa": "4.0.1",
"create-hash": "1.2.0",
"create-hmac": "1.1.7",
"elliptic": "6.5.2",
"inherits": "2.0.4",
"parse-asn1": "5.1.5",
"readable-stream": "3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "2.0.4",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
}
}
@@ -823,9 +789,9 @@
}
},
"chokidar": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
"integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
"integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
"dev": true,
"optional": true,
"requires": {
@@ -836,7 +802,7 @@
"is-binary-path": "2.1.0",
"is-glob": "4.0.1",
"normalize-path": "3.0.0",
"readdirp": "3.4.0"
"readdirp": "3.5.0"
},
"dependencies": {
"braces": {
@@ -1113,7 +1079,7 @@
"normalize-path": "3.0.0",
"p-limit": "2.3.0",
"schema-utils": "1.0.0",
"serialize-javascript": "2.1.2",
"serialize-javascript": "4.0.0",
"webpack-log": "2.0.0"
}
},
@@ -1123,24 +1089,6 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"create-ecdh": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
"integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
"dev": true,
"requires": {
"bn.js": "4.11.8",
"elliptic": "6.5.2"
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
},
"create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
@@ -1198,6 +1146,66 @@
"public-encrypt": "4.0.3",
"randombytes": "2.1.0",
"randomfill": "1.0.4"
},
"dependencies": {
"browserify-sign": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.1.0.tgz",
"integrity": "sha512-VYxo7cDCeYUoBZ0ZCy4UyEUCP3smyBd4DRQM5nrFS1jJjPJjX7rP3oLRpPoWfkhQfyJ0I9ZbHbKafrFD/SGlrg==",
"dev": true,
"requires": {
"bn.js": "5.1.3",
"browserify-rsa": "4.0.1",
"create-hash": "1.2.0",
"create-hmac": "1.1.7",
"elliptic": "6.5.3",
"inherits": "2.0.4",
"parse-asn1": "5.1.6",
"readable-stream": "3.6.0"
}
},
"create-ecdh": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
"dev": true,
"requires": {
"bn.js": "4.11.9",
"elliptic": "6.5.3"
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
},
"pbkdf2": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
"dev": true,
"requires": {
"create-hash": "1.2.0",
"create-hmac": "1.1.7",
"ripemd160": "2.0.2",
"safe-buffer": "5.1.2",
"sha.js": "2.4.11"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "2.0.4",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
}
}
},
"cyclist": {
@@ -1385,9 +1393,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
}
}
@@ -1466,9 +1474,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
}
}
@@ -1582,15 +1590,17 @@
"requires": {
"esrecurse": "4.2.1",
"estraverse": "4.3.0"
}
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "4.3.0"
},
"dependencies": {
"esrecurse": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
"dev": true,
"requires": {
"estraverse": "4.3.0"
}
}
}
},
"estraverse": {
@@ -1611,12 +1621,6 @@
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true
},
"events": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
"dev": true
},
"eventsource": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
@@ -2336,7 +2340,7 @@
"requires": {
"http-proxy": "1.18.1",
"is-glob": "4.0.1",
"lodash": "4.17.15",
"lodash": "4.17.20",
"micromatch": "3.1.10"
}
},
@@ -2490,7 +2494,7 @@
"dev": true,
"optional": true,
"requires": {
"binary-extensions": "2.0.0"
"binary-extensions": "2.1.0"
}
},
"is-buffer": {
@@ -2723,15 +2727,6 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"lcid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
"integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
"dev": true,
"requires": {
"invert-kv": "2.0.0"
}
},
"loader-runner": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
@@ -2760,9 +2755,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
},
"loglevel": {
@@ -2790,15 +2785,6 @@
"semver": "5.7.1"
}
},
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
"dev": true,
"requires": {
"p-defer": "1.0.0"
}
},
"map-cache": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -2831,17 +2817,6 @@
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
"mem": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
"integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
"dev": true,
"requires": {
"map-age-cleaner": "0.1.3",
"mimic-fn": "2.1.0",
"p-is-promise": "2.1.0"
}
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -2896,9 +2871,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
}
}
@@ -3073,9 +3048,9 @@
"dev": true
},
"node-forge": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true
},
"node-libs-browser": {
@@ -3109,6 +3084,12 @@
"vm-browserify": "1.1.2"
},
"dependencies": {
"events": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
"integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==",
"dev": true
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@@ -3275,23 +3256,6 @@
"integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
"dev": true
},
"os-locale": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
"integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
"dev": true,
"requires": {
"execa": "1.0.0",
"lcid": "2.0.0",
"mem": "4.3.0"
}
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
"integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
"dev": true
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -3360,12 +3324,26 @@
"integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
"dev": true,
"requires": {
"asn1.js": "4.10.1",
"asn1.js": "5.4.1",
"browserify-aes": "1.2.0",
"create-hash": "1.2.0",
"evp_bytestokey": "1.0.3",
"pbkdf2": "3.0.17",
"safe-buffer": "5.1.2"
},
"dependencies": {
"pbkdf2": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
"dev": true,
"requires": {
"create-hash": "1.2.0",
"create-hmac": "1.1.7",
"ripemd160": "2.0.2",
"safe-buffer": "5.1.2",
"sha.js": "2.4.11"
}
}
}
},
"parse-passwd": {
@@ -3576,11 +3554,36 @@
"safe-buffer": "5.1.2"
},
"dependencies": {
"asn1.js": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
"dev": true,
"requires": {
"bn.js": "4.11.8",
"inherits": "2.0.4",
"minimalistic-assert": "1.0.1"
}
},
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
},
"parse-asn1": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
"integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
"dev": true,
"requires": {
"asn1.js": "4.10.1",
"browserify-aes": "1.2.0",
"create-hash": "1.2.0",
"evp_bytestokey": "1.0.3",
"pbkdf2": "3.1.1",
"safe-buffer": "5.1.2"
}
}
}
},
@@ -3708,9 +3711,9 @@
}
},
"readdirp": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"dev": true,
"optional": true,
"requires": {
@@ -3896,12 +3899,12 @@
"dev": true
},
"selfsigned": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
"version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true,
"requires": {
"node-forge": "0.9.0"
"node-forge": "0.10.0"
}
},
"semver": {
@@ -3945,7 +3948,7 @@
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
"randombytes": "2.1.0"
}
},
"serve-index": {
@@ -4563,9 +4566,9 @@
"find-cache-dir": "2.1.0",
"is-wsl": "1.1.0",
"schema-utils": "1.0.0",
"serialize-javascript": "2.1.2",
"serialize-javascript": "4.0.0",
"source-map": "0.6.1",
"terser": "4.6.13",
"terser": "4.8.0",
"webpack-sources": "1.4.3",
"worker-farm": "1.7.0"
},
@@ -4875,9 +4878,9 @@
"integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==",
"dev": true,
"requires": {
"chokidar": "3.4.0",
"chokidar": "3.4.3",
"graceful-fs": "4.2.4",
"neo-async": "2.6.1",
"neo-async": "2.6.2",
"watchpack-chokidar2": "2.0.0"
}
},
@@ -4996,7 +4999,7 @@
"ajv": "6.12.2",
"ajv-keywords": "3.4.1",
"chrome-trace-event": "1.0.2",
"enhanced-resolve": "4.1.1",
"enhanced-resolve": "4.3.0",
"eslint-scope": "4.0.3",
"json-parse-better-errors": "1.0.2",
"loader-runner": "2.4.0",
@@ -5008,58 +5011,59 @@
"node-libs-browser": "2.2.1",
"schema-utils": "1.0.0",
"tapable": "1.1.3",
"terser-webpack-plugin": "1.4.3",
"watchpack": "1.7.2",
"terser-webpack-plugin": "1.4.5",
"watchpack": "1.7.4",
"webpack-sources": "1.4.3"
},
"dependencies": {
"enhanced-resolve": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
"integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
"dev": true,
"requires": {
"graceful-fs": "4.2.4",
"memory-fs": "0.5.0",
"tapable": "1.1.3"
},
"dependencies": {
"memory-fs": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
"integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
"dev": true,
"requires": {
"errno": "0.1.7",
"readable-stream": "2.3.7"
}
}
}
},
"neo-async": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"dev": true
}
}
},
"webpack-cli": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz",
"integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==",
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz",
"integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"cross-spawn": "6.0.5",
"enhanced-resolve": "4.1.0",
"enhanced-resolve": "4.3.0",
"findup-sync": "3.0.0",
"global-modules": "2.0.0",
"import-local": "2.0.0",
"interpret": "1.2.0",
"loader-utils": "1.2.3",
"interpret": "1.4.0",
"loader-utils": "1.4.0",
"supports-color": "6.1.0",
"v8-compile-cache": "2.0.3",
"yargs": "13.2.4"
},
"dependencies": {
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
"dev": true
},
"enhanced-resolve": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
"integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
"dev": true,
"requires": {
"graceful-fs": "4.2.4",
"memory-fs": "0.4.1",
"tapable": "1.1.3"
}
},
"loader-utils": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"dev": true,
"requires": {
"big.js": "5.2.2",
"emojis-list": "2.1.0",
"json5": "1.0.1"
}
}
"v8-compile-cache": "2.1.1",
"yargs": "13.3.2"
}
},
"webpack-dev-middleware": {
@@ -5109,7 +5113,7 @@
"p-retry": "3.0.1",
"portfinder": "1.0.26",
"schema-utils": "1.0.0",
"selfsigned": "1.10.7",
"selfsigned": "1.10.8",
"semver": "6.3.0",
"serve-index": "1.9.1",
"sockjs": "0.3.20",
@@ -5374,7 +5378,6 @@
"cliui": "5.0.0",
"find-up": "3.0.0",
"get-caller-file": "2.0.5",
"os-locale": "3.1.0",
"require-directory": "2.1.1",
"require-main-filename": "2.0.0",
"set-blocking": "2.0.0",
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@nymproject/nym-client-wasm": "^0.8.0",
"@nymproject/nym-client-wasm": "file:../pkg",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
@@ -11,7 +11,7 @@
"node-sass": "^4.14.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"react-scripts": "^4.0.0",
"reactstrap": "^8.5.1",
"request": "^2.79.0"
},
@@ -41,7 +41,6 @@
"hello-wasm-pack": "^0.1.0",
"react-app-rewired": "^2.1.6",
"wasm-loader": "^1.3.0",
"webpack": "^4.29.3",
"webpack-cli": "^3.1.0"
}
}
+2 -2
View File
@@ -42,8 +42,8 @@ class App extends React.Component {
const wasm = await import('@nymproject/nym-client-wasm');
this.setState({ wasm });
// Set up identity and client
let directory = "https://qa-directory.nymtech.net";
let _nymClient = new wasm.NymClient(directory);
let validator = "http://testnet-validator1.nymtech.net:8081";
let _nymClient = new wasm.NymClient(validator);
_nymClient.set_on_message(this.receivedMessage);
// Start the Nym client. Connects to a Nym gateway via websocket.
_nymClient = await _nymClient.initial_setup();
-18
View File
@@ -1,18 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The file has been placed there by the build script.
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/built.rs"));
+21 -24
View File
@@ -12,18 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::built_info;
use crypto::asymmetric::{encryption, identity};
use directory_client::DirectoryClient;
use futures::channel::mpsc;
use gateway_client::GatewayClient;
use js_sys::Promise;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::PacketMode;
use nymsphinx::preparer::MessagePreparer;
use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, NymTopology};
@@ -38,10 +36,12 @@ const DEFAULT_RNG: OsRng = OsRng;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_PACKET_MODE: PacketMode = PacketMode::VPN;
const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000;
#[wasm_bindgen]
pub struct NymClient {
directory_server: String,
validator_server: String,
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
// however, once we eventually combine this code with the native-client's, it will make things
@@ -66,7 +66,7 @@ pub struct NymClient {
#[wasm_bindgen]
impl NymClient {
#[wasm_bindgen(constructor)]
pub fn new(directory_server: String) -> Self {
pub fn new(validator_server: String) -> Self {
// for time being generate new keys each time...
let identity = identity::KeyPair::new_with_rng(&mut DEFAULT_RNG);
let encryption_keys = encryption::KeyPair::new_with_rng(&mut DEFAULT_RNG);
@@ -76,7 +76,7 @@ impl NymClient {
identity: Arc::new(identity),
encryption_keys: Arc::new(encryption_keys),
ack_key: Arc::new(ack_key),
directory_server,
validator_server,
message_preparer: None,
// received_keys: Default::default(),
topology: None,
@@ -149,6 +149,8 @@ impl NymClient {
client.self_recipient(),
DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_AVERAGE_ACK_DELAY,
DEFAULT_PACKET_MODE,
Some(DEFAULT_VPN_KEY_REUSE_LIMIT),
);
let received_processor = ReceivedMessagesProcessor::new(
@@ -186,23 +188,21 @@ impl NymClient {
.prepare_and_split_message(message_bytes, false, topology)
.expect("failed to split the message");
let mut socket_messages = Vec::with_capacity(split_message.len());
let mut mix_packets = Vec::with_capacity(split_message.len());
for message_chunk in split_message {
// don't bother with acks etc. for time being
let prepared_fragment = message_preparer
.prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient)
.await
.unwrap();
console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay);
socket_messages.push((
prepared_fragment.first_hop_address,
prepared_fragment.sphinx_packet,
));
mix_packets.push(prepared_fragment.mix_packet);
}
self.gateway_client
.as_mut()
.unwrap()
.batch_send_sphinx_packets(socket_messages)
.batch_send_mix_packets(mix_packets)
.await
.unwrap();
self
@@ -233,27 +233,24 @@ impl NymClient {
}
pub fn get_full_topology_json(&self) -> Promise {
let directory_client_config = directory_client::Config::new(self.directory_server.clone());
let directory_client = directory_client::Client::new(directory_client_config);
let validator_client_config = validator_client::Config::new(self.validator_server.clone());
let validator_client = validator_client::Client::new(validator_client_config);
future_to_promise(async move {
let topology = &directory_client.get_topology().await.unwrap();
let topology = &validator_client.get_active_topology().await.unwrap();
Ok(JsValue::from_serde(&topology).unwrap())
})
}
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
let directory_client_config = directory_client::Config::new(self.directory_server.clone());
let directory_client = directory_client::Client::new(directory_client_config);
let validator_client_config = validator_client::Config::new(self.validator_server.clone());
let validator_client = validator_client::Client::new(validator_client_config);
match directory_client.get_topology().await {
match validator_client.get_active_topology().await {
Err(err) => panic!(err),
Ok(topology) => {
let nym_topology: NymTopology = topology
.try_into()
.ok()
.expect("this is not a NYM topology!");
let version = built_info::PKG_VERSION;
nym_topology.filter_system_version(&version)
let nym_topology: NymTopology = topology.into();
let version = env!("CARGO_PKG_VERSION");
nym_topology.filter_system_version(version)
}
}
}
-1
View File
@@ -14,7 +14,6 @@
use wasm_bindgen::prelude::*;
pub(crate) mod built_info;
#[cfg(target_arch = "wasm32")]
mod client;
@@ -1,52 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crypto::asymmetric::identity;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
#[derive(Debug)]
pub enum ConversionError {
InvalidKeyError,
}
impl From<identity::SignatureError> for ConversionError {
fn from(_: identity::SignatureError) -> Self {
ConversionError::InvalidKeyError
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CocoPresence {
pub location: String,
pub host: String,
pub pub_key: String,
pub last_seen: u64,
pub version: String,
}
impl TryInto<topology::coco::Node> for CocoPresence {
type Error = ConversionError;
fn try_into(self) -> Result<topology::coco::Node, Self::Error> {
Ok(topology::coco::Node {
location: self.location,
host: self.host,
pub_key: identity::PublicKey::from_base58_string(self.pub_key)?,
last_seen: self.last_seen,
version: self.version,
})
}
}
@@ -1,79 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
#[derive(Debug)]
pub enum ConversionError {
InvalidKeyError,
InvalidAddress(io::Error),
}
impl From<identity::SignatureError> for ConversionError {
fn from(_: identity::SignatureError) -> Self {
ConversionError::InvalidKeyError
}
}
impl From<encryption::EncryptionKeyError> for ConversionError {
fn from(_: encryption::EncryptionKeyError) -> Self {
ConversionError::InvalidKeyError
}
}
impl From<io::Error> for ConversionError {
fn from(err: io::Error) -> Self {
ConversionError::InvalidAddress(err)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GatewayPresence {
pub location: String,
pub client_listener: String,
pub mixnet_listener: String,
pub identity_key: String,
pub sphinx_key: String,
pub last_seen: u64,
pub version: String,
}
impl TryInto<topology::gateway::Node> for GatewayPresence {
type Error = ConversionError;
fn try_into(self) -> Result<topology::gateway::Node, Self::Error> {
let resolved_mix_hostname = self.mixnet_listener.to_socket_addrs()?.next();
if resolved_mix_hostname.is_none() {
return Err(ConversionError::InvalidAddress(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
)));
}
Ok(topology::gateway::Node {
location: self.location,
client_listener: self.client_listener,
mixnet_listener: resolved_mix_hostname.unwrap(),
identity_key: identity::PublicKey::from_base58_string(self.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(self.sphinx_key)?,
last_seen: self.last_seen,
version: self.version,
})
}
}
@@ -1,71 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crypto::asymmetric::encryption;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
#[derive(Debug)]
pub enum ConversionError {
InvalidKeyError,
InvalidAddress(io::Error),
}
impl From<encryption::EncryptionKeyError> for ConversionError {
fn from(_: encryption::EncryptionKeyError) -> Self {
ConversionError::InvalidKeyError
}
}
impl From<io::Error> for ConversionError {
fn from(err: io::Error) -> Self {
ConversionError::InvalidAddress(err)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixNodePresence {
pub location: String,
pub host: String,
pub pub_key: String,
pub layer: u64,
pub last_seen: u64,
pub version: String,
}
impl TryInto<topology::mix::Node> for MixNodePresence {
type Error = ConversionError;
fn try_into(self) -> Result<topology::mix::Node, Self::Error> {
let resolved_hostname = self.host.to_socket_addrs()?.next();
if resolved_hostname.is_none() {
return Err(ConversionError::InvalidAddress(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
)));
}
Ok(topology::mix::Node {
location: self.location,
host: resolved_hostname.unwrap(),
pub_key: encryption::PublicKey::from_base58_string(self.pub_key)?,
layer: self.layer,
last_seen: self.last_seen,
version: self.version,
})
}
}
@@ -1,21 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod coconodes;
pub mod gateways;
pub mod mixnodes;
pub mod providers;
pub mod topology;
pub use self::topology::{Topology, TopologyConversionError};
@@ -1,124 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{coconodes, gateways, mixnodes, providers};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use topology::{MixLayer, NymTopology};
#[derive(Debug)]
pub enum TopologyConversionError {
CocoError(self::coconodes::ConversionError),
GatewayError(self::gateways::ConversionError),
MixError(self::mixnodes::ConversionError),
}
impl From<self::coconodes::ConversionError> for TopologyConversionError {
fn from(err: self::coconodes::ConversionError) -> Self {
TopologyConversionError::CocoError(err)
}
}
impl From<self::gateways::ConversionError> for TopologyConversionError {
fn from(err: self::gateways::ConversionError) -> Self {
TopologyConversionError::GatewayError(err)
}
}
impl From<self::mixnodes::ConversionError> for TopologyConversionError {
fn from(err: self::mixnodes::ConversionError) -> Self {
TopologyConversionError::MixError(err)
}
}
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub coco_nodes: Vec<coconodes::CocoPresence>,
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
pub gateway_nodes: Vec<gateways::GatewayPresence>,
}
impl TryInto<NymTopology> for Topology {
type Error = TopologyConversionError;
fn try_into(self) -> Result<NymTopology, TopologyConversionError> {
use std::collections::HashMap;
let mut coco_nodes = Vec::with_capacity(self.coco_nodes.len());
for coco in self.coco_nodes.into_iter() {
coco_nodes.push(coco.try_into()?)
}
let mut mixes = HashMap::new();
for mix in self.mix_nodes.into_iter() {
let layer = mix.layer as MixLayer;
let layer_entry = mixes.entry(layer).or_insert(Vec::new());
layer_entry.push(mix.try_into()?)
}
let mut gateways = Vec::with_capacity(self.gateway_nodes.len());
for gate in self.gateway_nodes.into_iter() {
gateways.push(gate.try_into()?)
}
Ok(NymTopology::new(coco_nodes, mixes, gateways))
}
}
#[cfg(test)]
mod converting_mixnode_presence_into_topology_mixnode {
use super::*;
#[test]
fn it_returns_error_on_unresolvable_hostname() {
use topology::mix;
let unresolvable_hostname = "foomp.foomp.foomp:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: unresolvable_hostname.to_string(),
pub_key: "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<mix::Node, self::mixnodes::ConversionError> = mix_presence.try_into();
assert!(result.is_err());
}
#[test]
#[cfg_attr(feature = "offline-test", ignore)]
fn it_returns_resolved_ip_on_resolvable_hostname() {
let resolvable_hostname = "nymtech.net:1234";
let mix_presence = mixnodes::MixNodePresence {
location: "".to_string(),
host: resolvable_hostname.to_string(),
pub_key: "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF".to_string(),
layer: 0,
last_seen: 0,
version: "".to_string(),
};
let result: Result<topology::mix::Node, self::mixnodes::ConversionError> =
mix_presence.try_into();
result.unwrap();
// assert!(result.is_ok())
}
}
@@ -1,148 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::requests::health_check_get::Request as HealthCheckRequest;
use crate::requests::metrics_mixes_get::Request as MetricsMixRequest;
use crate::requests::metrics_mixes_post::Request as MetricsMixPost;
use crate::requests::presence_coconodes_post::Request as PresenceCocoNodesPost;
use crate::requests::presence_gateways_post::Request as PresenceGatewayPost;
use crate::requests::presence_mixnodes_post::Request as PresenceMixNodesPost;
use crate::requests::presence_providers_post::Request as PresenceProvidersPost;
use crate::requests::presence_topology_get::Request as PresenceTopologyRequest;
use directory_client_models::metrics::{MixMetric, PersistedMixMetric};
use directory_client_models::presence::{
coconodes::CocoPresence, gateways::GatewayPresence, mixnodes::MixNodePresence,
providers::MixProviderPresence,
};
use requests::{health_check_get::HealthCheckResponse, DirectoryGetRequest, DirectoryPostRequest};
pub use directory_client_models::{
metrics,
presence::{self, Topology},
};
pub mod requests;
pub struct Config {
pub base_url: String,
}
impl Config {
pub fn new(base_url: String) -> Self {
Config { base_url }
}
}
pub trait DirectoryClient {
fn new(config: Config) -> Self;
}
pub struct Client {
base_url: String,
reqwest_client: reqwest::Client,
}
impl DirectoryClient for Client {
fn new(config: Config) -> Client {
let reqwest_client = reqwest::Client::new();
Client {
base_url: config.base_url,
reqwest_client,
}
}
}
impl Client {
async fn post<R: DirectoryPostRequest>(
&self,
request: R,
) -> reqwest::Result<reqwest::Response> {
self.reqwest_client
.post(&request.url())
.json(request.json_payload())
.send()
.await
}
async fn get<R: DirectoryGetRequest>(&self, request: R) -> reqwest::Result<R::JSONResponse> {
self.reqwest_client
.get(&request.url())
.send()
.await?
.json()
.await
}
pub async fn get_healthcheck(&self) -> reqwest::Result<HealthCheckResponse> {
let req = HealthCheckRequest::new(&self.base_url);
self.get(req).await
}
pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result<reqwest::Response> {
let req = MetricsMixPost::new(&self.base_url, metrics);
self.post(req).await
}
pub async fn get_mix_metrics(&self) -> reqwest::Result<Vec<PersistedMixMetric>> {
let req = MetricsMixRequest::new(&self.base_url);
self.get(req).await
}
pub async fn post_coconode_presence(
&self,
presence: CocoPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceCocoNodesPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn post_gateway_presence(
&self,
presence: GatewayPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceGatewayPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn post_mixnode_presence(
&self,
presence: MixNodePresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceMixNodesPost::new(&self.base_url, presence);
self.post(req).await
}
// this should be soft-deprecated as the whole concept of provider will
// be removed in the next topology rework
pub async fn post_provider_presence(
&self,
presence: MixProviderPresence,
) -> reqwest::Result<reqwest::Response> {
let req = PresenceProvidersPost::new(&self.base_url, presence);
self.post(req).await
}
pub async fn get_topology(&self) -> reqwest::Result<Topology> {
let req = PresenceTopologyRequest::new(&self.base_url);
self.get(req).await
}
}
#[cfg(test)]
pub(crate) fn client_test_fixture(base_url: &str) -> Client {
Client {
base_url: base_url.to_string(),
reqwest_client: reqwest::Client::new(),
}
}
@@ -1,85 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryGetRequest, DirectoryRequest};
use serde::{Deserialize, Serialize};
const PATH: &str = "/api/healthcheck";
#[derive(Deserialize, Serialize)]
pub struct HealthCheckResponse {
pub ok: bool,
}
pub struct Request {
base_url: String,
path: String,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = HealthCheckResponse;
fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
}
#[cfg(test)]
mod healthcheck_requests {
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("GET", "/api/healthcheck").with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let res = client.get_healthcheck().await;
assert!(res.is_err());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_200_status() {
let json = r#"{
"ok": true
}"#;
let _m = mock("GET", "/api/healthcheck")
.with_status(200)
.with_body(json)
.create();
let client = client_test_fixture(&mockito::server_url());
let res = client.get_healthcheck().await;
assert!(res.unwrap().ok);
_m.assert();
}
}
}
@@ -1,45 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod health_check_get;
pub mod metrics_mixes_get;
pub mod metrics_mixes_post;
pub mod presence_coconodes_post;
pub mod presence_gateways_post;
pub mod presence_mixnodes_post;
pub mod presence_providers_post;
pub mod presence_topology_get;
use serde::{de::DeserializeOwned, Serialize};
pub(crate) trait DirectoryRequest {
fn url(&self) -> String;
}
pub(crate) trait DirectoryGetRequest: DirectoryRequest {
// perhaps the name of this is not the best because it's technically not a JSON,
// but something that can be deserialised from JSON.
// I'm open to all suggestions on how to rename it
type JSONResponse: DeserializeOwned;
fn new(base_url: &str) -> Self;
}
pub(crate) trait DirectoryPostRequest: DirectoryRequest {
// Similarly this, it's something that can be serialized into a JSON
type Payload: Serialize + ?Sized;
fn new(base_url: &str, payload: Self::Payload) -> Self;
fn json_payload(&self) -> &Self::Payload;
}
@@ -1,99 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::coconodes::CocoPresence;
const PATH: &str = "/api/presence/coconodes";
pub struct Request {
base_url: String,
path: String,
payload: CocoPresence,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryPostRequest for Request {
type Payload = CocoPresence;
fn json_payload(&self) -> &CocoPresence {
&self.payload
}
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod presence_coconodes_post_request {
use super::*;
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_coconode_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_coconode_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
#[cfg(test)]
mod fixtures {
use crate::presence::coconodes::CocoPresence;
pub fn new_presence() -> CocoPresence {
CocoPresence {
location: "foomp".to_string(),
host: "foo.com".to_string(),
pub_key: "abc".to_string(),
last_seen: 666,
version: "0.2.0".to_string(),
}
}
}
}
@@ -1,100 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::gateways::GatewayPresence;
const PATH: &str = "/api/presence/gateways";
pub struct Request {
base_url: String,
path: String,
payload: GatewayPresence,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryPostRequest for Request {
type Payload = GatewayPresence;
fn json_payload(&self) -> &GatewayPresence {
&self.payload
}
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod presence_gateways_post_request {
use super::*;
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_gateway_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_gateway_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
#[cfg(test)]
mod fixtures {
use crate::presence::gateways::GatewayPresence;
pub fn new_presence() -> GatewayPresence {
GatewayPresence {
location: "foomp".to_string(),
client_listener: "foo.com".to_string(),
mixnet_listener: "foo.com".to_string(),
identity_key: "def".to_string(),
sphinx_key: "abc".to_string(),
last_seen: 0,
version: "0.1.0".to_string(),
}
}
}
}
@@ -1,100 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::mixnodes::MixNodePresence;
const PATH: &str = "/api/presence/mixnodes";
pub struct Request {
base_url: String,
path: String,
payload: MixNodePresence,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryPostRequest for Request {
type Payload = MixNodePresence;
fn json_payload(&self) -> &MixNodePresence {
&self.payload
}
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod presence_mixnodes_post_request {
use super::*;
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_mixnode_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_mixnode_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
#[cfg(test)]
mod fixtures {
use crate::presence::mixnodes::MixNodePresence;
pub fn new_presence() -> MixNodePresence {
MixNodePresence {
location: "foomp".to_string(),
host: "foo.com".to_string(),
pub_key: "abc".to_string(),
layer: 1,
last_seen: 0,
version: "0.1.0".to_string(),
}
}
}
}
@@ -1,100 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::presence::providers::MixProviderPresence;
const PATH: &str = "/api/presence/mixproviders";
pub struct Request {
base_url: String,
path: String,
payload: MixProviderPresence,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryPostRequest for Request {
type Payload = MixProviderPresence;
fn json_payload(&self) -> &MixProviderPresence {
&self.payload
}
fn new(base_url: &str, payload: Self::Payload) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
}
#[cfg(test)]
mod presence_providers_post_request {
use super::*;
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_provider_presence(presence).await;
assert_eq!(400, result.unwrap().status());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_201() {
let json = r#"{
"ok": true
}"#;
let _m = mock("POST", PATH).with_status(201).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let presence = fixtures::new_presence();
let result = client.post_provider_presence(presence).await;
assert!(result.is_ok());
_m.assert();
}
}
#[cfg(test)]
mod fixtures {
use crate::presence::providers::MixProviderPresence;
pub fn new_presence() -> MixProviderPresence {
MixProviderPresence {
location: "foomp".to_string(),
client_listener: "foo.com".to_string(),
mixnet_listener: "foo.com".to_string(),
pub_key: "abc".to_string(),
registered_clients: vec![],
last_seen: 0,
version: "0.1.0".to_string(),
}
}
}
}
@@ -1,248 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryGetRequest, DirectoryRequest};
use crate::presence::Topology;
const PATH: &str = "/api/presence/topology";
pub struct Request {
base_url: String,
path: String,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = Topology;
fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
}
#[cfg(test)]
mod topology_requests {
use super::*;
use crate::client_test_fixture;
use mockito::mock;
#[cfg(test)]
mod on_a_400_status {
use super::*;
#[tokio::test]
async fn it_returns_an_error() {
let _m = mock("GET", PATH)
.with_status(400)
.with_body("bad body")
.create();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_topology().await;
assert!(result.is_err());
_m.assert();
}
}
#[cfg(test)]
mod on_a_200 {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_200_status_and_a_correct_topology() {
let json = fixtures::topology_response_json();
let _m = mock("GET", PATH).with_status(200).with_body(json).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.get_topology().await;
assert_eq!(
1_575_915_097_085_539_300,
result.unwrap().coco_nodes.first().unwrap().last_seen
);
_m.assert();
}
}
#[cfg(test)]
pub mod fixtures {
#[cfg(test)]
pub fn topology_response_json() -> String {
r#"{
"cocoNodes": [
{
"location": "unknown",
"host": "3.8.244.109:4000",
"pubKey": "AAAAAAAAAAEKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEFuhRpy4HfcuxwcRI9sIWMo_LVmbk19g1gfMRlBrmZqoEQL6rDApVLZ9eMp-5IQK8WLlZpWf4Zjy7kZolARAyp_rHUQkH4PrDjgoPrKbm6qK_iejYpL7qx28Q3VeInMpwMIMaSbbW9y36sEVtGc2I0Iu5vS0sp8ESiVlQ5NaBz72deZ8oKJJ4IEPPHP99-b0UQX80fVIrNM88mMzKy0bHri9NFlmIG-e0G1cqmw_ry3XWGQkcr1M5RuNa6oX50w5QawAEVxd5FP5bE8bS4x54Csof11sQWUTwMp6Q7_3H7ZCTSlKSqujlOhmfqSHfGPO2sDIYPHDhDzjakZpKAZWWhn_hiR6DfPpomQ01ZYUhVKKSMxz7_VPjsQplP0bZXA2gfnkADUN8UQ0N9g_usIw73r4aZsOviMsRM8oByvsjVfUWc4_HTLSdnQyImFkHz9CiCmrIYL2dYQRePRatWggvBAyeRzntxI4jDqLKiBdi54ZlAKgV6MCRaJ7Bu7BtmLXrtK4sawAED3QYxuvOSZrbZdUr4yG-U9yVvJ9Klkf-5Mo4EYp3qTL2KBB6_LrZepjAQqp486YkZ03mTIezcsZ48EboXVTWKBZ3QnTI5tX-j4gGxQb7klOJc97qJkDxsvpz4F0ChgCUIZhpIItWHia7_R3Gi-b5siLIdQdUho9isn3kiDGm6t0NED2Bgy3ZxxQwzqsBZm4kPr2_fPX4YyvIoP9895YcGjZyE5iiRC_TE41RJmB1GZYdxegTMq3lNDllKgiqaiPgawAEJASDkmZHTwlg9YOev5OWpQD-FnhPkqVNo_QcDyRu9eoGcWSGFp2sYqjG2SpmiXq0VNnAO7AcKxRzDFu7TjfhlU3Kt0uTKIcrWVU1zFNbJNMjYEq90pp50nowwx8INz20IXET2ZNX6kIXYFCsEvPLZFlG2OoL6xg3uQS1qMl3lIS_VxdO_JfVe0rT65WsJ_P4Nkc1jYiuNPHY6d_iFO0BVYqX0sOCX73GC_TT13BR0jnPwDAVw0rGtYHsXBb8TKOsawAEZIClauuT1V3qOZnb7uRZhFXO-PKTxgc1LCzJt2ChOrMZaBpjlkf3IPpJ2UF4JH4kGaDeBf2k_S-FLAs3drK21efbi5P6_a4QTxAiiRimXGoQIyvOg462s6kP_ZRFufo8YYQHS4olaOeqU4564dNskg_uBPsFMz_2GNOhmn_15cJqP1jfkyD49Z16GTS5YLHgVl9bJKqyvLuypsToLbt1BJzipEP0L2OohuRm-_MvqvwwWKyjNQsubgee1K728d9AawAEBkGggcNVCtXyhoSqi3_w0tVxtkAYeud8sBeAtZHGs06me_QL8co0MFLlO-zdkUb4ZBq08rFEbgLOma8_3whleM8NIPaHNISp1q3IsIhB5zdXcZoGsqLixODBFHtID3YEHAlr4f9T_yh11yJ95xGCl_6Y37hpwLQVGyrfSfccM24mVFqnV3TT5Wdq3ile-jesUx1Q2G1yK_xVqc6itmk-kDuBjyZgzYi1-jsIXAjnhM9G7t8J_Bv5yGGZhLK2dCzM=",
"type": "validator",
"lastSeen": 1575915097085539300,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "3.9.129.61:4000",
"pubKey": "AAAAAAAAAAMKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEEv6RMevAQmLGkeK0uJKnMPPAtm8GgXjWSQijYdnxlPh5SJSNeJUbPZKWFFWdk8yIFXKa8jnzETtdGFKgUUt5AVUDpTBmEdwaHCzlFhXrttshy0V5OhPUlV8cGABmxbagMYm0bFPg0r-snSkrB9YG6wqJYQVeIMOCGYCPbHmDA8R_0-h8VkRKWs1d9KvQOK4kShqgZtYN71KJW8uDE4q2jsGDVvxFt1AgmU9b93xsXF17KrpZy5WxlLZ73HtnTD_oawAED4vd_rK-Kx_n8x_OdDiiEOPUlYDlDCQUqenU9XHKH3B6ijfkJ368wd3LDDVStjDwNORrAyUSw_VlSNUpd1XLC8d17gTaIq5ZI2fWuwwZaoN1JCsYU8fQ6USgtIehQX7IPP8EkFuNmuCBCmpr4schtYniGe9J8Q4dsV-TYPr2uLJkdx1r7luzF--I22k7NfQQM14QDci_0kgrgmZ54CJGkjXyOhCppBXg3fqLC6aFvT3ZocfiiXBJt0huGgPMDtYsawAECLh8KUdNsDolERwJ8v04bS5jI_KKf7uUnCHWuCELwbJSUI3OK1ufS1qSpauvSzVQSbrhEzrEfwQn4VtxQxJlX4UdDU-R-hafiZvVC6DLLAbuORBAC3FScn9W58CnezH4DvCp_w7nftDfdxeuungbZT9XaxS3iNC6PnFsWF6WM3DxMwrzOrFe6wEEoTSPe1mcUDrtwM5UksIvJr6MBRAXrdl0IdBTQr7cLwKe_KYi4siwdjfJEJtOh7oxQBxBg2UkawAEJAPZK2Gg2MQwpxdDT24lNQHF7FVfkO_LuhJwn0RbwNDSVeA4P6-tWL5TkCpqr8xYHfwQ6Z3ILfpGCZr8PspwIoRzqZHQ16f8Pq9xnr0hLEI9BOQU0FS2EtuyPgju5iwsAJAfehUzu6kNLphuLGsXoIZdXDG5mbylwh9JzAVXTwgaR0hNqyXVJxgbt7jcYaSEBFcMGV-hjXyVVNzBleE-G9o_noI_KWU4Ce7K-qOMcewMKfy_VEw-gVaD6dHz6AMoawAEE9XuOLwRttvKybAssZ9gsK-_YRUwuFOeRDIr3NX___9bx6pCc18adCIlH_8EJWFwXZ05ZpNNE88mYx7ZQ3aqaArZJRoWeZeKhqH_s05V10xbzkYX71G5cqz--8vr9ZlQRb2BeETF_Tdq_PLk7qbT8WTGIoq7ZwyDRQTgzvkCgyzj_hBLh2o7sSVNgUo38SFUTMn7YtvVFYlSrTDE3WKE-T-nh5SWdDBxgDTc3Bw8JpzNH-WkoJ4Lim7sB4Op1gEUawAEW4-kenlffwsNr_3b3aV0YuusLpxB03sxPzQ5B0CWNiVtbja1Z4tWhKGUUrdq_eUgMV0y5Of-BqNi5FspAQnhJBFSSxtOzRGV1h3qyUTksfZyed9z8zPI-ZPP9XXm7hYgJgDz_kxte-NfS9UG9q5AZetHUN4kGxXutjjzfUQZ9yTvhBKgKgTI2Dp_R_jZrWQ8F1BoWzIJzjddT1K2MvCQEkARYw08isbOeFmCwgVUcjxYZO45WyOmLQA7QJRL9WvA=",
"type": "validator",
"lastSeen": 1575915097388409000,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "3.9.222.1:4000",
"pubKey": "AAAAAAAAAAQKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAECh9xcxpjOp1r7kiNIgrI9GgAlvXwgHkTchOxUiyOzTq6FDWdGN64KiC3NDeyGTg8FmzvGzS3jREeJqOdr4G9ZGtWkauAITgLFiH62t-YntRslhr8_1shxlmzKiNKJN_QFflEq79pZIlWtp3N8LIHMvXRtl-zt2DMze4s02XDmEkviyVE4CkQUDtCc-2MfPT4JcmEFqtFIxjrXn18SbYg3c6XUQHsGIkuDrKuCTRlpC8kvmM0uVoIeWdmwDlZk4jUawAEJhRwK5ozjqIWRP1bFzBPS9VhaJnfKU9PeFYtN5beiAHrYr2ylIB3yDfmAQUdKDowDUm5nfJATejEjEnrTGxh70QtfoNV391rSns3F71tBwY62KLaNr8qnVfeSFHV3FcQTMHHF_8mDb5_11Rj6aiMvW0y6eetHo7CDPMdEyDPmok_U2ZM5BzOUnwjT21HtnvcKxKKwHJ_QGfnAHPyDIhNOMgxJCrVazOidLCHeYGpyCLw1ipeTyKOQX0_ByB8dH6AawAEGV1GuF5SSlT67B1ityPJK2ZwXjeeKB4gGdCG3qRtWxLTZfGhVm7YAYm2f5tw_wrsJAZ9FubVhateGg0ZN67NxZtsvOOejXz6743f7ijnQopPgd_8pH-iVf6BEcSO8ZdcHxNRUTayzjVLs99bwMo2zaPevW4X4G_bN4mh---aPkdGYHwaiklzUhqJ-eqycrYAFyjyEXaPBXLQm1rpczqluNvnKbd8Q9LZWukgm7_uWv_HxufIvdWgoq8bAt78UU3oawAEP9VDehhqrQG5-WHMB66XVxo1TgMM8aVV0SwAq3lCRkpiFBz_9kw8T1F9Hx2AiNrEGT1QLbdMkpms1cG_5gBBahQofdt_NmUs1jfTFXY9iyMy1Q7A6ZYaLP8Z6q-orc1cKqySY-BJZQ_CpGFfXS0OVniFDQ6v78ytPK7K-yRgT1PxFgm3rZqrG0Tjbrpsg2PUL5S5fuXfMhUosP0uoLj0D1guWAR9Y7kfFBIXaTSFMoa8fghVBUTRNhK9f72a8SxQawAEOiv71taLjKqaaWQ_QjcDhWbvjG1EnsCyI0toNjGkcF19x4Vk-5NC96_4ioUGz404IC0XN03roRnibRT_78D9vZFVCWCqve9EjdF5TcApx03zIP4JT2g2q0MKIGgGrwt4Pz6LO6yOfMm7B8Yraps8IV-nP1w7K1m9XKP_FvH8egl5GHJe-_omlC2YyL_b28jMLENbxDFD-3KPjZFBhSLrRukX2PlayYTwEiTtokA2R9_11vQvJgP8KFEjGHg6zsAMawAEBn2H_hz2knb8ltnpEA5YSKVcV3nUtojkCNi_WUz7xUKd7efw1oI_lbnKrS7HkyC0JkQUZ1pCWUlSXNmgjMEhsn823a1LFzpV7rOv4vayYvvFX61hB9R78VjpyxJiYpDwRZLiUY3AK4WY8NqFDbjXR7rT4CkFHEf-VhSQQ8ZNvlpod1nmeVQVizHH9e7Tq7wsWz-LWEk3Hx6LmcrgDsL79LZYG9JXU5IdvG8RvLNx9cSwEI8yxcchpISAaot7UoYQ=",
"type": "validator",
"lastSeen": 1575915094734973000,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "3.9.102.214:4000",
"pubKey": "AAAAAAAAAAIKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEOVCUN3EwiVroS5-TOq2o7hYSxphK9X0G23N-IBZ0Tr1Rl8XEiJ-OEy0rqnAKwmhAZJWnx3u8oXqbZtOWIZmzQSpcoxhgwfhdmTZJCqT2RVzZyeFItX4sVeilEP3z2xdsJs8-a1kg6UZnx1s1BNLBo7eZrreZygWojPCIDBn03fSAflXoVc5PpY2CGy5MA_IgWgSYBHDdoZEtigp_amjqK7Us44Db20XpLxMXfbahiqa7WKNnMgi6Ca2H67VtaaD8awAEF3zbE1nZRAa7a8vbU25c80YBYJBaW8P6FwXQI-K0Xk5MakwYeMMnIrm6w6IS_0XAO5YlD453GLqnxY8H1BEnRpfOnT7PE4el9mJ8MuYQMo6R2up0lGCmYM0YA9FORjroM3ng69SEPfJPCReG7LfJkERl_m2U403ertDRBYrlqCDagDfyI500srBcMrjSvV3oNouyyx3yZUrjLQfbHhDteQFsYdmakJs8Y-Q9-5MXCcrz6Qa4xwv522Euv0CCxkHcawAEYjfsU_zDhUZA1ey1aquWXlFOnx-iEALqxW1slDYHwQ1M2SILc-v_E6i1doa5e_bAZHVezBHFAlaNAVedNyHFFJxYAqAK3hbzbvl2glw3Q6h_rTXElymloqtaqVFIJ-oUWWOHsZBmu8EDA-HzvGCiBa_GbRaVfh2lE4ObeMXoJrEm_5dbxxeEic2l3IYeIz40N9ooQQOkQcOZdY4AXWYCavIAwWEJBjLtptJgCLu9a_zM1S5GsiyJHpdDs46WbP0EawAEWZ-95Sf0YAHujxRNLdXgpqe0ZF8loVwzZfvyMvqaxF1Ug274BqHuY_c5NdPAzuqoTwjfEn8NKEoaNqlumM75FUYbaTd7mXvk4WVYWjVnkO40dfQjRB7DYhvj0LBlbndAJ4wJIA2ilPYgjZsXVbNNh3e2j3u9eABd0VaFMbSb8Sz5_31r8HzoWmPJs3HiyuyANGFUA6CvAnMN6K3b-D8BhFZU_nPUTgu80o8_n6LQt-XWbaC_mTHzsnOjzBiPJxlYawAEW3bmOEtStH2T8q7vMkhchImp2-hg9MFYGBmEe9sSByTn3NUf8eksqXOC1dUjHkXoZm298FgUYLkNdnlxWpf993j5mEDoFxjcTB7scBD7k6nu6Nrs_wK0-seS8gsHrx9UK7GwAsi10q82Cm4PFyAtrWjmy_d9WLHuZt6VIOKunTs8cf0FwNUiMcvZsruqIFJcP7iWxdiFdUkh65P_iCz1ZEjJcj2GEZoq4v3a3by1aizGPaaiKc1jd_T-XJg_YpncawAEWnstu5b9WiZv0x8xfsiMk6YRlU0Cnj5svxLLXz_8drvwAa--GBY5yH0ke2EM6udMEi2EPeFcGTe6Sjs0YEhSbY7Uad_8suD2J4tIWJSWBbiyvh7rSqzv57m7BlsVcHfQJn_wNH-UlC9xkx8vg-LwfN8_FlxvHNPTc7XZG3lKYbwpUWlZxAziOYT1VQ-2K2bQQBBMdix-ht_SjccL1Dc2dP5kDazQ8yZV_8xnyeheazEedWe63uutfkHlZRg9YwP8=",
"type": "validator",
"lastSeen": 1575915094967382800,
"version": "0.1.0"
}
],
"mixNodes": [
{
"location": "unknown",
"host": "35.176.155.107:1789",
"pubKey": "zSob16499jT7C3S3ky4GihNOjlU6aLfSRkf1xAxOwV0=",
"layer": 3,
"lastSeen": 1575915096805374500,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "18.130.86.190:1789",
"pubKey": "vCdpFc0NvW0NSqsuTxtjFtiSY35aXesgT3JNA8sSIXk=",
"layer": 1,
"lastSeen": 1575915097370376000,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "3.10.22.152:1789",
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
"layer": 1,
"lastSeen": 1575915097639423500,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "35.178.213.77:1789",
"pubKey": "nkkrUjgL8UJk05QydvWvFSvtRB6nmeV8RMvH5540J3s=",
"layer": 2,
"lastSeen": 1575915097895166500,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "52.56.99.196:1789",
"pubKey": "whHuBuEc6zyOZOquKbuATaH4Crml61V_3Y-MztpWhF4=",
"layer": 2,
"lastSeen": 1575915096255174700,
"version": "0.1.0"
},
{
"location": "unknown",
"host": "3.9.12.238:1789",
"pubKey": "vk5Sr-Xyi0cTbugACv8U42ZJ6hs6cGDox0rpmXY94Fc=",
"layer": 3,
"lastSeen": 1575915096497827600,
"version": "0.1.0"
}
],
"mixProviderNodes": [
{
"location": "unknown",
"clientListener": "3.8.176.11:8888",
"mixnetListener": "3.8.176.11:9999",
"pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=",
"registeredClients": [
{
"pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo="
},
{
"pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc="
},
{
"pubKey": "COGdpfhmzNGR6YX820GqJIkjOihL8mr6-h-d3JlTDFA="
}
],
"lastSeen": 1575915097358694100,
"version": "0.1.0"
},
{
"location": "unknown",
"clientListener": "3.8.176.12:8888",
"mixnetListener": "3.8.176.12:9999",
"pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=",
"registeredClients": [
{
"pubKey": "7fbk4oGQNlTW-tnWjVz8rWtKrtAicTsiNWgO98sqMyk="
},
{
"pubKey": "w1bfLpnd3rWu5JczB0nQfnE2S6nUCbx2AA7HDE48DQo="
}
],
"lastSeen": 1575915097869025000,
"version": "0.1.0"
}
],
"gatewayNodes": [
{
"location": "unknown",
"clientListener": "3.8.176.11:8888",
"mixnetListener": "3.8.176.11:9999",
"identityKey": "B9xz9V6jpp1fEbDkeyR5f8miorw9bzXGKoMbKnaxkD41",
"sphinxKey": "3KCpz1HCD8DqnQjemT1uuBZipmHFXM4V5btxLXwvM1gG",
"registeredClients": [
{
"pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo="
},
{
"pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc="
}
],
"lastSeen": 1575915097358694100,
"version": "0.1.0"
},
{
"location": "unknown",
"clientListener": "3.8.176.12:8888",
"mixnetListener": "3.8.176.12:9999",
"identityKey": "CdqJCedY5d1geJNDjUqnEx8zF7mKjb6PCZ6k3T6xhxD",
"sphinxKey": "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF",
"registeredClients": [
{
"pubKey": "UE-7r6-bpw0b4T3GxOBVxlg02psx23DF2p5Tuf-OBSE="
},
{
"pubKey": "UnZuLpzq64_EPtIcr1Fd-5AESBCBLFnDMDsjUaOqrUA="
}
],
"lastSeen": 1575915097869025000,
"version": "0.1.0"
}
]
}"#.to_string()
}
}
}
@@ -25,7 +25,7 @@ use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::authentication::iv::AuthenticationIV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys, DEFAULT_RNG};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use nymsphinx::forwarding::packet::MixPacket;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::Duration;
@@ -320,9 +320,9 @@ impl GatewayClient {
}
}
pub async fn batch_send_sphinx_packets(
pub async fn batch_send_mix_packets(
&mut self,
packets: Vec<(NymNodeRoutingAddress, SphinxPacket)>,
packets: Vec<MixPacket>,
) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -333,8 +333,8 @@ impl GatewayClient {
let messages: Vec<_> = packets
.into_iter()
.map(|(address, packet)| {
BinaryRequest::new_forward_request(address, packet).into_ws_message(
.map(|mix_packet| {
BinaryRequest::new_forward_request(mix_packet).into_ws_message(
self.shared_key
.as_ref()
.expect("no shared key present even though we're authenticated!"),
@@ -347,10 +347,9 @@ impl GatewayClient {
}
// TODO: possibly make responses optional
pub async fn send_sphinx_packet(
pub async fn send_mix_packet(
&mut self,
address: NymNodeRoutingAddress,
packet: SphinxPacket,
mix_packet: MixPacket,
) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -360,7 +359,7 @@ impl GatewayClient {
}
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
// be more explicit in the naming?
let msg = BinaryRequest::new_forward_request(address, packet).into_ws_message(
let msg = BinaryRequest::new_forward_request(mix_packet).into_ws_message(
self.shared_key
.as_ref()
.expect("no shared key present even though we're authenticated!"),
@@ -183,23 +183,14 @@ pub(crate) enum SocketState {
impl SocketState {
pub(crate) fn is_available(&self) -> bool {
match self {
SocketState::Available(_) => true,
_ => false,
}
matches!(self, SocketState::Available(_))
}
pub(crate) fn is_partially_delegated(&self) -> bool {
match self {
SocketState::PartiallyDelegated(_) => true,
_ => false,
}
matches!(self, SocketState::PartiallyDelegated(_))
}
pub(crate) fn is_established(&self) -> bool {
match self {
SocketState::Available(_) | SocketState::PartiallyDelegated(_) => true,
_ => false,
}
matches!(self, SocketState::Available(_) | SocketState::PartiallyDelegated(_))
}
}
@@ -1,23 +1,15 @@
[package]
name = "directory-client"
name = "metrics-client"
version = "0.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
offline-test = []
[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.10", features = ["json"] }
## internal
directory-client-models = { path = "models" }
[dev-dependencies]
mockito = "0.23.0"
tokio = { version = "0.2", features = ["macros"] }
@@ -0,0 +1,74 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod models;
pub mod requests;
use crate::models::metrics::{MixMetric, MixMetricInterval, PersistedMixMetric};
use crate::requests::metrics_mixes_get::Request as MetricsMixRequest;
use crate::requests::metrics_mixes_post::Request as MetricsMixPost;
pub struct Config {
pub base_url: String,
}
impl Config {
pub fn new(base_url: String) -> Self {
Config { base_url }
}
}
pub struct Client {
base_url: String,
reqwest_client: reqwest::Client,
}
impl Client {
pub fn new(config: Config) -> Client {
let reqwest_client = reqwest::Client::new();
Client {
base_url: config.base_url,
reqwest_client,
}
}
pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result<MixMetricInterval> {
let req = MetricsMixPost::new(&self.base_url, metrics);
self.reqwest_client
.post(&req.url())
.json(req.json_payload())
.send()
.await?
.json()
.await
}
pub async fn get_mix_metrics(&self) -> reqwest::Result<Vec<PersistedMixMetric>> {
let req = MetricsMixRequest::new(&self.base_url);
self.reqwest_client
.get(&req.url())
.send()
.await?
.json()
.await
}
}
#[cfg(test)]
pub(crate) fn client_test_fixture(base_url: &str) -> Client {
Client {
base_url: base_url.to_string(),
reqwest_client: reqwest::Client::new(),
}
}
@@ -31,3 +31,9 @@ pub struct MixMetric {
pub received: u64,
pub sent: HashMap<String, u64>,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixMetricInterval {
pub next_report_in: u64,
}
@@ -13,4 +13,3 @@
// limitations under the License.
pub mod metrics;
pub mod presence;
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryGetRequest, DirectoryRequest};
use crate::metrics::PersistedMixMetric;
const PATH: &str = "/api/metrics/mixes";
pub struct Request {
@@ -22,21 +19,17 @@ pub struct Request {
path: String,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryGetRequest for Request {
type JSONResponse = Vec<PersistedMixMetric>;
fn new(base_url: &str) -> Self {
impl Request {
pub(crate) fn new(base_url: &str) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
}
}
pub(crate) fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
#[cfg(test)]
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::{DirectoryPostRequest, DirectoryRequest};
use crate::metrics::MixMetric;
use crate::models::metrics::MixMetric;
const PATH: &str = "/api/metrics/mixes";
@@ -23,25 +22,22 @@ pub struct Request {
payload: MixMetric,
}
impl DirectoryRequest for Request {
fn url(&self) -> String {
impl Request {
pub(crate) fn url(&self) -> String {
format!("{}{}", self.base_url, self.path)
}
}
impl DirectoryPostRequest for Request {
type Payload = MixMetric;
fn json_payload(&self) -> &MixMetric {
&self.payload
}
fn new(base_url: &str, payload: Self::Payload) -> Self {
pub(crate) fn new(base_url: &str, payload: MixMetric) -> Self {
Request {
base_url: base_url.to_string(),
path: PATH.to_string(),
payload,
}
}
pub(crate) fn json_payload(&self) -> &MixMetric {
&self.payload
}
}
#[cfg(test)]
@@ -59,7 +55,7 @@ mod metrics_post_request {
let _m = mock("POST", PATH).with_status(400).create();
let client = client_test_fixture(&mockito::server_url());
let result = client.post_mix_metrics(fixtures::new_metric()).await;
assert_eq!(400, result.unwrap().status());
assert!(result.is_err());
_m.assert();
}
}
@@ -69,7 +65,7 @@ mod metrics_post_request {
use super::*;
#[tokio::test]
async fn it_returns_a_response_with_200() {
let json = fixtures::mix_metrics_response_json();
let json = fixtures::metrics_interval_json();
let _m = mock("POST", "/api/metrics/mixes")
.with_status(201)
.with_body(json)
@@ -83,7 +79,7 @@ mod metrics_post_request {
#[cfg(test)]
mod fixtures {
use crate::metrics::MixMetric;
use crate::models::metrics::MixMetric;
pub fn new_metric() -> MixMetric {
MixMetric {
@@ -94,17 +90,11 @@ mod metrics_post_request {
}
#[cfg(test)]
pub fn mix_metrics_response_json() -> String {
pub fn metrics_interval_json() -> String {
r#"
{
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
"sent": {
"35.178.213.77:1789": 1,
"52.56.99.196:1789": 2
},
"received": 10,
"timestamp": 1576061080635800000
}
{
"nextReportIn": 5
}
"#
.to_string()
}
@@ -12,4 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// placeholder for Ethereum / ERC20 bridge integration
pub mod metrics_mixes_get;
pub mod metrics_mixes_post;
@@ -0,0 +1,156 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::connection_manager::{ConnectionManager, ConnectionManagerSender};
use futures::channel::oneshot;
use futures::future::AbortHandle;
use log::*;
use nymsphinx::framing::packet::FramedSphinxPacket;
use nymsphinx::params::PacketMode;
use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
use std::time::Duration;
pub struct Config {
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_reconnection_attempts: u32,
}
impl Config {
pub fn new(
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_reconnection_attempts: u32,
) -> Self {
Config {
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_reconnection_attempts,
}
}
}
pub struct Client {
connections_managers: HashMap<SocketAddr, (ConnectionManagerSender, AbortHandle)>,
maximum_reconnection_backoff: Duration,
initial_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_reconnection_attempts: u32,
}
impl Client {
pub fn new(config: Config) -> Client {
Client {
connections_managers: HashMap::new(),
initial_reconnection_backoff: config.initial_reconnection_backoff,
maximum_reconnection_backoff: config.maximum_reconnection_backoff,
initial_connection_timeout: config.initial_connection_timeout,
maximum_reconnection_attempts: config.maximum_reconnection_attempts,
}
}
async fn start_new_connection_manager(
&mut self,
address: SocketAddr,
) -> Result<(ConnectionManagerSender, AbortHandle), io::Error> {
let conn_manager = ConnectionManager::new(
address,
self.initial_reconnection_backoff,
self.maximum_reconnection_backoff,
self.initial_connection_timeout,
self.maximum_reconnection_attempts,
)
.await?;
let (sender, abort_handle) = conn_manager.spawn_abortable();
Ok((sender, abort_handle))
}
// if wait_for_response is set to true, we will get information about any possible IO errors
// as well as (once implemented) received replies, however, this will also cause way longer
// waiting periods
pub async fn send(
&mut self,
address: NymNodeRoutingAddress,
packet: SphinxPacket,
packet_mode: PacketMode,
wait_for_response: bool,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let socket_address = address.into();
if !self.connections_managers.contains_key(&socket_address) {
debug!(
"There is no existing connection to {:?} - it will be established now",
address
);
let (new_manager_sender, abort_handle) =
match self.start_new_connection_manager(socket_address).await {
Ok(res) => res,
Err(err) => {
debug!(
"failed to establish initial connection to {} - {}",
socket_address, err
);
return Err(err);
}
};
self.connections_managers
.insert(socket_address, (new_manager_sender, abort_handle));
}
let manager = self.connections_managers.get_mut(&socket_address).unwrap();
let framed_packet = FramedSphinxPacket::new(packet, packet_mode);
let (res_tx, res_rx) = if wait_for_response {
let (res_tx, res_rx) = oneshot::channel();
(Some(res_tx), Some(res_rx))
} else {
(None, None)
};
if let Err(err) = manager.0.unbounded_send((framed_packet, res_tx)) {
warn!(
"Connection manager to {} has failed - {}",
socket_address, err
);
self.connections_managers.remove(&socket_address);
return Err(io::Error::new(io::ErrorKind::BrokenPipe, err));
}
if let Some(res_rx) = res_rx {
res_rx.await.unwrap()
} else {
Ok(())
}
}
}
impl Drop for Client {
fn drop(&mut self) {
for (_, abort_handle) in self.connections_managers.values() {
abort_handle.abort()
}
}
}
@@ -19,19 +19,19 @@ use futures::future::{abortable, AbortHandle};
use futures::task::Poll;
use futures::{SinkExt, StreamExt};
use log::*;
use nymsphinx::SphinxPacket;
use nymsphinx::framing::packet::FramedSphinxPacket;
use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::runtime::Handle;
mod reconnector;
mod writer;
pub(crate) type ResponseSender = Option<oneshot::Sender<io::Result<()>>>;
pub(crate) type ConnectionManagerSender = mpsc::UnboundedSender<(SphinxPacket, ResponseSender)>;
type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(SphinxPacket, ResponseSender)>;
pub(crate) type ConnectionManagerSender =
mpsc::UnboundedSender<(FramedSphinxPacket, ResponseSender)>;
type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(FramedSphinxPacket, ResponseSender)>;
enum ConnectionState<'a> {
Writing(ConnectionWriter),
@@ -46,6 +46,7 @@ pub(crate) struct ConnectionManager<'a> {
maximum_reconnection_backoff: Duration,
reconnection_backoff: Duration,
maximum_reconnection_attempts: u32,
state: ConnectionState<'a>,
}
@@ -62,59 +63,68 @@ impl<'a> ConnectionManager<'static> {
reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
connection_timeout: Duration,
) -> ConnectionManager<'a> {
maximum_reconnection_attempts: u32,
) -> Result<ConnectionManager<'a>, io::Error> {
let (conn_tx, conn_rx) = mpsc::unbounded();
// the blocking call here is fine as initially we want to wait the timeout interval (at most) anyway:
let tcp_stream_res = std::net::TcpStream::connect_timeout(&address, connection_timeout);
// we MUST succeed in making initial connection. We don't want to end up in reconnection
// loop to something we have never managed to connect (and possibly never will)
let initial_state = match tcp_stream_res {
Ok(stream) => {
let tokio_stream = tokio::net::TcpStream::from_std(stream).unwrap();
debug!("managed to establish initial connection to {}", address);
ConnectionState::Writing(ConnectionWriter::new(tokio_stream))
}
Err(e) => {
warn!("failed to establish initial connection to {} within {:?} ({}). Going into reconnection mode", address, connection_timeout, e);
ConnectionState::Reconnecting(ConnectionReconnector::new(
address,
reconnection_backoff,
maximum_reconnection_backoff,
))
}
Err(err) => return Err(err),
};
ConnectionManager {
Ok(ConnectionManager {
conn_tx,
conn_rx,
address,
maximum_reconnection_backoff,
reconnection_backoff,
maximum_reconnection_attempts,
state: initial_state,
}
})
}
async fn run(mut self) {
while let Some(msg) = self.conn_rx.next().await {
let (msg_content, res_ch) = msg;
let res = self.handle_new_packet(msg_content).await;
if let Some(res_ch) = res_ch {
if let Err(e) = res_ch.send(res) {
error!(
"failed to send response on the channel to the caller! - {:?}",
e
let (framed_packet, res_ch) = msg;
match self.handle_new_packet(framed_packet).await {
None => {
warn!(
"We reached maximum number of attempts trying to reconnect to {}",
self.address
);
return;
}
Some(res) => {
if let Some(res_ch) = res_ch {
if let Err(e) = res_ch.send(res) {
error!(
"failed to send response on the channel to the caller! - {:?}",
e
);
}
}
}
}
}
}
/// consumes Self and returns channel for communication as well as an `AbortHandle`
pub(crate) fn start_abortable(self, handle: &Handle) -> (ConnectionManagerSender, AbortHandle) {
pub(crate) fn spawn_abortable(self) -> (ConnectionManagerSender, AbortHandle) {
let sender_clone = self.conn_tx.clone();
let (abort_fut, abort_handle) = abortable(self.run());
handle.spawn(async move { abort_fut.await });
tokio::spawn(async move { abort_fut.await });
(sender_clone, abort_handle)
}
@@ -122,7 +132,7 @@ impl<'a> ConnectionManager<'static> {
// Possible future TODO: `Framed<...>` is both a Sink and a Stream,
// so it is possible to read any responses we might receive (it is also duplex, so that could be
// done while writing packets themselves). But it'd require slight additions to `SphinxCodec`
async fn handle_new_packet(&mut self, packet: SphinxPacket) -> io::Result<()> {
async fn handle_new_packet(&mut self, packet: FramedSphinxPacket) -> Option<io::Result<()>> {
// we don't do a match here as it's possible to transition from ConnectionState::Reconnecting to ConnectionState::Writing
// in this function call. And if that happens, we want to send the packet we have received.
if let ConnectionState::Reconnecting(conn_reconnector) = &mut self.state {
@@ -130,22 +140,27 @@ impl<'a> ConnectionManager<'static> {
let new_connection = match futures::poll(conn_reconnector).await {
Poll::Pending => {
debug!("The packet is getting dropped - there's nowhere to send it");
return Err(io::Error::new(
return Some(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"connection is broken - reconnection is in progress",
));
)));
}
Poll::Ready(conn) => conn,
};
debug!("Managed to reconnect to {}!", self.address);
self.state = ConnectionState::Writing(ConnectionWriter::new(new_connection));
match new_connection {
Ok(new_conn) => {
debug!("Managed to reconnect to {}!", self.address);
self.state = ConnectionState::Writing(ConnectionWriter::new(new_conn));
}
Err(_) => return None,
}
}
// we must be in writing state if we are here, either by being here from beginning or just
// transitioning from reconnecting
if let ConnectionState::Writing(conn_writer) = &mut self.state {
if let Err(e) = conn_writer.send(packet).await {
return if let Err(e) = conn_writer.send(packet).await {
warn!(
"Failed to forward message - {:?}. Starting reconnection procedure...",
e
@@ -154,14 +169,15 @@ impl<'a> ConnectionManager<'static> {
self.address,
self.reconnection_backoff,
self.maximum_reconnection_backoff,
self.maximum_reconnection_attempts,
));
return Err(io::Error::new(
Some(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"connection is broken - reconnection is in progress",
));
)))
} else {
return Ok(());
}
Some(Ok(()))
};
}
unreachable!();
@@ -22,11 +22,14 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
pub(crate) struct MaximumReconnectionCountReached;
pub(crate) struct ConnectionReconnector<'a> {
address: SocketAddr,
connection: BoxFuture<'a, io::Result<tokio::net::TcpStream>>,
current_retry_attempt: u32,
maximum_reconnection_attempts: u32,
current_backoff_delay: tokio::time::Delay,
maximum_reconnection_backoff: Duration,
@@ -39,12 +42,14 @@ impl<'a> ConnectionReconnector<'a> {
address: SocketAddr,
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
maximum_reconnection_attempts: u32,
) -> ConnectionReconnector<'a> {
ConnectionReconnector {
address,
connection: tokio::net::TcpStream::connect(address).boxed(),
current_backoff_delay: tokio::time::delay_for(Duration::new(0, 0)), // if we can re-establish connection on first try without any backoff that's perfect
current_retry_attempt: 0,
maximum_reconnection_attempts,
maximum_reconnection_backoff,
initial_reconnection_backoff,
}
@@ -52,7 +57,7 @@ impl<'a> ConnectionReconnector<'a> {
}
impl<'a> Future for ConnectionReconnector<'a> {
type Output = tokio::net::TcpStream;
type Output = Result<tokio::net::TcpStream, MaximumReconnectionCountReached>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// see if we are still in exponential backoff
@@ -72,6 +77,11 @@ impl<'a> Future for ConnectionReconnector<'a> {
self.address, e, self.current_retry_attempt
);
// checked if we reached the maximum attempt count
if self.current_retry_attempt == self.maximum_reconnection_attempts {
return Poll::Ready(Err(MaximumReconnectionCountReached));
}
// we failed to re-establish connection - continue exponential backoff
// according to https://github.com/tokio-rs/tokio/issues/1953 there's an undocumented
@@ -102,7 +112,7 @@ impl<'a> Future for ConnectionReconnector<'a> {
Poll::Pending
}
Poll::Ready(Ok(conn)) => Poll::Ready(conn),
Poll::Ready(Ok(conn)) => Poll::Ready(Ok(conn)),
}
}
}
@@ -14,8 +14,8 @@
use futures::task::{Context, Poll};
use futures::Sink;
use nymsphinx::framing::{SphinxCodec, SphinxCodecError};
use nymsphinx::SphinxPacket;
use nymsphinx::framing::codec::{SphinxCodec, SphinxCodecError};
use nymsphinx::framing::packet::FramedSphinxPacket;
use std::pin::Pin;
use tokio_util::codec::Framed;
@@ -31,14 +31,14 @@ impl ConnectionWriter {
}
}
impl Sink<SphinxPacket> for ConnectionWriter {
impl Sink<FramedSphinxPacket> for ConnectionWriter {
type Error = SphinxCodecError;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.framed_connection).poll_ready(cx)
}
fn start_send(mut self: Pin<&mut Self>, item: SphinxPacket) -> Result<(), Self::Error> {
fn start_send(mut self: Pin<&mut Self>, item: FramedSphinxPacket) -> Result<(), Self::Error> {
Pin::new(&mut self.framed_connection).start_send(item)
}
@@ -0,0 +1,76 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client::{Client, Config};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use std::time::Duration;
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
type MixForwardingReceiver = mpsc::UnboundedReceiver<MixPacket>;
/// A specialisation of client such that it forwards any received packets on the channel into the
/// mix network immediately, i.e. will not try to listen for any responses.
pub struct PacketForwarder {
mixnet_client: Client,
packet_receiver: MixForwardingReceiver,
}
impl PacketForwarder {
pub fn new(
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_reconnection_attempts: u32,
) -> (PacketForwarder, MixForwardingSender) {
let client_config = Config::new(
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_reconnection_attempts,
);
let (packet_sender, packet_receiver) = mpsc::unbounded();
(
PacketForwarder {
mixnet_client: Client::new(client_config),
packet_receiver,
},
packet_sender,
)
}
pub async fn run(&mut self) {
while let Some(mix_packet) = self.packet_receiver.next().await {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_mode = mix_packet.packet_mode();
let sphinx_packet = mix_packet.into_sphinx_packet();
// we don't care about responses, we just want to fire packets
// as quickly as possible
if let Err(err) = self
.mixnet_client
.send(next_hop, sphinx_packet, packet_mode, false)
.await
{
debug!("failed to forward the packet - {}", err)
}
}
}
}
+3 -269
View File
@@ -12,272 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::connection_manager::{ConnectionManager, ConnectionManagerSender};
use futures::channel::oneshot;
use futures::future::AbortHandle;
use log::*;
use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::runtime::Handle;
mod connection_manager;
pub struct Config {
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
}
impl Config {
pub fn new(
initial_reconnection_backoff: Duration,
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
) -> Self {
Config {
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
}
}
}
pub struct Client {
runtime_handle: Handle,
connections_managers: HashMap<SocketAddr, (ConnectionManagerSender, AbortHandle)>,
maximum_reconnection_backoff: Duration,
initial_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
}
impl Client {
pub fn new(config: Config) -> Client {
Client {
// if the function is not called within tokio runtime context, this will panic
// but perhaps the code should be better structured to completely avoid this call
runtime_handle: Handle::try_current()
.expect("The client MUST BE used within tokio runtime context"),
connections_managers: HashMap::new(),
initial_reconnection_backoff: config.initial_reconnection_backoff,
maximum_reconnection_backoff: config.maximum_reconnection_backoff,
initial_connection_timeout: config.initial_connection_timeout,
}
}
async fn start_new_connection_manager(
&mut self,
address: SocketAddr,
) -> (ConnectionManagerSender, AbortHandle) {
let (sender, abort_handle) = ConnectionManager::new(
address,
self.initial_reconnection_backoff,
self.maximum_reconnection_backoff,
self.initial_connection_timeout,
)
.await
.start_abortable(&self.runtime_handle);
(sender, abort_handle)
}
// if wait_for_response is set to true, we will get information about any possible IO errors
// as well as (once implemented) received replies, however, this will also cause way longer
// waiting periods
pub async fn send(
&mut self,
address: NymNodeRoutingAddress,
packet: SphinxPacket,
wait_for_response: bool,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let socket_address = address.into();
if !self.connections_managers.contains_key(&socket_address) {
debug!(
"There is no existing connection to {:?} - it will be established now",
address
);
let (new_manager_sender, abort_handle) =
self.start_new_connection_manager(socket_address).await;
self.connections_managers
.insert(socket_address, (new_manager_sender, abort_handle));
}
let manager = self.connections_managers.get_mut(&socket_address).unwrap();
if wait_for_response {
let (res_tx, res_rx) = oneshot::channel();
manager.0.unbounded_send((packet, Some(res_tx))).unwrap();
res_rx.await.unwrap()
} else {
manager.0.unbounded_send((packet, None)).unwrap();
Ok(())
}
}
}
impl Drop for Client {
fn drop(&mut self) {
for (_, abort_handle) in self.connections_managers.values() {
abort_handle.abort()
}
}
}
/*
The below tests weren't extremely reliable to begin with, however,
to restore them as they were before, we'd need to expose some kind of 'SphinxPacket::test_fixture()`
function.
*/
//
// #[cfg(test)]
// mod tests {
// use super::*;
// use std::str;
// use std::time;
// use tokio::prelude::*;
//
// const SERVER_MSG_LEN: usize = 16;
// const CLOSE_MESSAGE: [u8; SERVER_MSG_LEN] = [0; SERVER_MSG_LEN];
//
// struct DummyServer {
// received_buf: Vec<Vec<u8>>,
// listener: tokio::net::TcpListener,
// }
//
// impl DummyServer {
// async fn new(address: SocketAddr) -> Self {
// DummyServer {
// received_buf: Vec::new(),
// listener: tokio::net::TcpListener::bind(address).await.unwrap(),
// }
// }
//
// fn get_received(&self) -> Vec<Vec<u8>> {
// self.received_buf.clone()
// }
//
// // this is only used in tests so slightly higher logging levels are fine
// async fn listen_until(mut self, close_message: &[u8]) -> Self {
// let (mut socket, _) = self.listener.accept().await.unwrap();
// loop {
// let mut buf = [0u8; SERVER_MSG_LEN];
// match socket.read(&mut buf).await {
// Ok(n) if n == 0 => {
// info!("Remote connection closed");
// return self;
// }
// Ok(n) => {
// info!("received ({}) - {:?}", n, str::from_utf8(buf[..n].as_ref()));
//
// if buf[..n].as_ref() == close_message {
// info!("closing...");
// socket.shutdown(std::net::Shutdown::Both).unwrap();
// return self;
// } else {
// self.received_buf.push(buf[..n].to_vec());
// }
// }
// Err(e) => {
// panic!("failed to read from socket; err = {:?}", e);
// }
// };
// }
// }
// }
//
// #[test]
// fn client_reconnects_to_server_after_it_went_down() {
// let mut rt = tokio::runtime::Runtime::new().unwrap();
// let addr = "127.0.0.1:6000".parse().unwrap();
// let reconnection_backoff = Duration::from_secs(1);
// let timeout = Duration::from_secs(1);
// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout);
//
// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()];
//
// let dummy_server = rt.block_on(DummyServer::new(addr));
// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE));
//
// let mut c = rt.enter(|| Client::new(client_config));
//
// for msg in &messages_to_send {
// rt.block_on(c.send(addr, msg.clone(), true)).unwrap();
// }
//
// // kill server
// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true))
// .unwrap();
// let received_messages = rt
// .block_on(finished_dummy_server_future)
// .unwrap()
// .get_received();
//
// assert_eq!(received_messages, messages_to_send);
//
// // try to send - go into reconnection
// let post_kill_message = [3u8; SERVER_MSG_LEN].to_vec();
//
// // we are trying to send to killed server
// assert!(rt
// .block_on(c.send(addr, post_kill_message.clone(), true))
// .is_err());
//
// let new_dummy_server = rt.block_on(DummyServer::new(addr));
// let new_server_future = rt.spawn(new_dummy_server.listen_until(&CLOSE_MESSAGE));
//
// // keep sending after we leave reconnection backoff and reconnect
// loop {
// if rt
// .block_on(c.send(addr, post_kill_message.clone(), true))
// .is_ok()
// {
// break;
// }
// rt.block_on(
// async move { tokio::time::delay_for(time::Duration::from_millis(50)).await },
// );
// }
//
// // kill the server to ensure it actually got the message
// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true))
// .unwrap();
// let new_received_messages = rt.block_on(new_server_future).unwrap().get_received();
// assert_eq!(post_kill_message.to_vec(), new_received_messages[0]);
// }
//
// #[test]
// fn server_receives_all_sent_messages_when_up() {
// let mut rt = tokio::runtime::Runtime::new().unwrap();
// let addr = "127.0.0.1:6001".parse().unwrap();
// let reconnection_backoff = Duration::from_secs(2);
// let timeout = Duration::from_secs(1);
// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout);
//
// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()];
//
// let dummy_server = rt.block_on(DummyServer::new(addr));
// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE));
//
// let mut c = rt.enter(|| Client::new(client_config));
//
// for msg in &messages_to_send {
// rt.block_on(c.send(addr, msg.clone(), true)).unwrap();
// }
//
// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true))
// .unwrap();
//
// // the server future should have already been resolved
// let received_messages = rt
// .block_on(finished_dummy_server_future)
// .unwrap()
// .get_received();
//
// assert_eq!(received_messages, messages_to_send);
// }
// }
pub mod client;
pub(crate) mod connection_manager;
pub mod forwarder;
@@ -8,4 +8,12 @@ edition = "2018"
[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.10", features = ["json"] }
crypto = { path = "../../crypto" }
topology = { path = "../../topology" }
[dev-dependencies]
mockito = "0.23.0"
tokio = { version = "0.2", features = ["macros"] }
+237 -5
View File
@@ -12,10 +12,242 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
use crate::models::gateway::GatewayRegistrationInfo;
use crate::models::mixmining::{BatchMixStatus, MixStatus};
use crate::models::mixnode::MixRegistrationInfo;
use crate::models::topology::Topology;
use crate::rest_requests::{
ActiveTopologyGet, ActiveTopologyGetResponse, BatchMixStatusPost, GatewayRegisterPost,
MixRegisterPost, MixStatusPost, NodeUnregisterDelete, RESTRequest, RESTRequestError,
ReputationPatch, TopologyGet, TopologyGetResponse,
};
use serde::Deserialize;
pub mod models;
pub mod rest_requests;
// for ease of use
type Result<T> = std::result::Result<T, ValidatorClientError>;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ErrorResponse {
error: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct OkResponse {
ok: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub(crate) enum DefaultRESTResponse {
Ok(OkResponse),
Error(ErrorResponse),
}
#[derive(Debug)]
pub enum ValidatorClientError {
RESTRequestError(RESTRequestError),
ReqwestClientError(reqwest::Error),
ValidatorError(String),
}
impl From<RESTRequestError> for ValidatorClientError {
fn from(err: RESTRequestError) -> Self {
ValidatorClientError::RESTRequestError(err)
}
}
impl From<reqwest::Error> for ValidatorClientError {
fn from(err: reqwest::Error) -> Self {
ValidatorClientError::ReqwestClientError(err)
}
}
pub struct Config {
base_url: String,
}
impl Config {
pub fn new<S: Into<String>>(base_url: S) -> Self {
Config {
base_url: base_url.into(),
}
}
}
pub struct Client {
config: Config,
reqwest_client: reqwest::Client,
}
impl Client {
pub fn new(config: Config) -> Self {
let reqwest_client = reqwest::Client::new();
Client {
config,
reqwest_client,
}
}
async fn make_rest_request<R: RESTRequest>(
&self,
request: R,
) -> Result<R::ExpectedJsonResponse> {
let mut req_builder = self
.reqwest_client
.request(R::METHOD, request.url().clone());
if let Some(json_payload) = request.json_payload() {
// if applicable, attach payload
req_builder = req_builder.json(json_payload)
}
Ok(req_builder.send().await?.json().await?)
}
pub async fn register_mix(&self, mix_registration_info: MixRegistrationInfo) -> Result<()> {
let req = MixRegisterPost::new(
&self.config.base_url,
None,
None,
Some(mix_registration_info),
)?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(_) => Ok(()),
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn register_gateway(
&self,
gateway_registration_info: GatewayRegistrationInfo,
) -> Result<()> {
let req = GatewayRegisterPost::new(
&self.config.base_url,
None,
None,
Some(gateway_registration_info),
)?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(ok_res) => {
if ok_res.ok {
Ok(())
} else {
Err(ValidatorClientError::ValidatorError(
"received ok response with false".into(),
))
}
}
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn unregister_node(&self, node_id: &str) -> Result<()> {
let req =
NodeUnregisterDelete::new(&self.config.base_url, Some(vec![node_id]), None, None)?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(ok_res) => {
if ok_res.ok {
Ok(())
} else {
Err(ValidatorClientError::ValidatorError(
"received ok response with false".into(),
))
}
}
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn set_reputation(&self, node_id: &str, new_reputation: i64) -> Result<()> {
let new_rep_string = new_reputation.to_string();
let query_param_values = vec![&*new_rep_string];
let query_param_keys = ReputationPatch::query_param_keys();
let query_params = query_param_keys
.into_iter()
.zip(query_param_values.into_iter())
.collect();
let req = ReputationPatch::new(
&self.config.base_url,
Some(vec![node_id]),
Some(query_params),
None,
)?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(ok_res) => {
if ok_res.ok {
Ok(())
} else {
Err(ValidatorClientError::ValidatorError(
"received ok response with false".into(),
))
}
}
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn get_topology(&self) -> Result<Topology> {
let req = TopologyGet::new(&self.config.base_url, None, None, None)?;
match self.make_rest_request(req).await? {
TopologyGetResponse::Ok(topology) => Ok(topology),
TopologyGetResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn get_active_topology(&self) -> Result<Topology> {
let req = ActiveTopologyGet::new(&self.config.base_url, None, None, None)?;
match self.make_rest_request(req).await? {
ActiveTopologyGetResponse::Ok(topology) => Ok(topology),
ActiveTopologyGetResponse::Error(err) => {
Err(ValidatorClientError::ValidatorError(err.error))
}
}
}
pub async fn post_mixmining_status(&self, status: MixStatus) -> Result<()> {
let req = MixStatusPost::new(&self.config.base_url, None, None, Some(status))?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(ok_res) => {
if ok_res.ok {
Ok(())
} else {
Err(ValidatorClientError::ValidatorError(
"received ok response with false".into(),
))
}
}
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
pub async fn post_batch_mixmining_status(&self, batch_status: BatchMixStatus) -> Result<()> {
let req = BatchMixStatusPost::new(&self.config.base_url, None, None, Some(batch_status))?;
match self.make_rest_request(req).await? {
DefaultRESTResponse::Ok(ok_res) => {
if ok_res.ok {
Ok(())
} else {
Err(ValidatorClientError::ValidatorError(
"received ok response with false".into(),
))
}
}
DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
}
}
}
#[cfg(test)]
pub(crate) fn client_test_fixture(base_url: &str) -> Client {
Client {
config: Config::new(base_url),
reqwest_client: reqwest::Client::new(),
}
}
@@ -0,0 +1,131 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::models::node::NodeInfo;
use crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
#[derive(Debug)]
pub enum ConversionError {
InvalidIdentityKeyError(identity::KeyRecoveryError),
InvalidSphinxKeyError(encryption::KeyRecoveryError),
InvalidAddress(io::Error),
}
impl From<encryption::KeyRecoveryError> for ConversionError {
fn from(err: encryption::KeyRecoveryError) -> Self {
ConversionError::InvalidSphinxKeyError(err)
}
}
impl From<identity::KeyRecoveryError> for ConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
ConversionError::InvalidIdentityKeyError(err)
}
}
// used for gateways to register themselves
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GatewayRegistrationInfo {
#[serde(flatten)]
pub(crate) node_info: NodeInfo,
pub(crate) clients_host: String,
}
impl GatewayRegistrationInfo {
pub fn new(
mix_host: String,
clients_host: String,
identity_key: String,
sphinx_key: String,
version: String,
location: String,
incentives_address: Option<String>,
) -> Self {
GatewayRegistrationInfo {
node_info: NodeInfo {
mix_host,
identity_key,
sphinx_key,
version,
location,
incentives_address: incentives_address.unwrap_or_else(|| "".to_string()),
},
clients_host,
}
}
}
// actual entry in topology
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisteredGateway {
#[serde(flatten)]
pub(crate) gateway_info: GatewayRegistrationInfo,
pub(crate) registration_time: i64,
pub(crate) reputation: i64,
}
impl RegisteredGateway {
pub fn identity(&self) -> String {
self.gateway_info.node_info.identity_key.clone()
}
pub fn mixnet_listener(&self) -> String {
self.gateway_info.node_info.mix_host.clone()
}
pub fn clients_listener(&self) -> String {
self.gateway_info.clients_host.clone()
}
}
impl TryInto<topology::gateway::Node> for RegisteredGateway {
type Error = ConversionError;
fn try_into(self) -> Result<topology::gateway::Node, Self::Error> {
let resolved_mix_hostname = self
.gateway_info
.node_info
.mix_host
.to_socket_addrs()
.map_err(ConversionError::InvalidAddress)?
.next()
.ok_or_else(|| {
ConversionError::InvalidAddress(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
))
})?;
Ok(topology::gateway::Node {
location: self.gateway_info.node_info.location,
mixnet_listener: resolved_mix_hostname,
client_listener: self.gateway_info.clients_host,
identity_key: identity::PublicKey::from_base58_string(
self.gateway_info.node_info.identity_key,
)?,
sphinx_key: encryption::PublicKey::from_base58_string(
self.gateway_info.node_info.sphinx_key,
)?,
registration_time: self.registration_time,
reputation: self.reputation,
version: self.gateway_info.node_info.version,
})
}
}
@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
/// A notification sent to the validators to let them know whether a given mix is
/// currently up or down (based on whether it's mixing packets)
pub struct MixStatus {
pub pub_key: String,
pub ip_version: String,
pub up: bool,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
/// A notification sent to the validators to let them know whether a given set of mixes is
/// currently up or down (based on whether it's mixing packets)
pub struct BatchMixStatus {
pub status: Vec<MixStatus>,
}
@@ -0,0 +1,127 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::models::node::NodeInfo;
use crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::io;
use std::net::ToSocketAddrs;
#[derive(Debug)]
pub enum ConversionError {
InvalidIdentityKeyError(identity::KeyRecoveryError),
InvalidSphinxKeyError(encryption::KeyRecoveryError),
InvalidAddress(io::Error),
}
impl From<encryption::KeyRecoveryError> for ConversionError {
fn from(err: encryption::KeyRecoveryError) -> Self {
ConversionError::InvalidSphinxKeyError(err)
}
}
impl From<identity::KeyRecoveryError> for ConversionError {
fn from(err: identity::KeyRecoveryError) -> Self {
ConversionError::InvalidIdentityKeyError(err)
}
}
// used for mixnode to register themselves
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixRegistrationInfo {
#[serde(flatten)]
pub(crate) node_info: NodeInfo,
pub(crate) layer: u64,
}
impl MixRegistrationInfo {
pub fn new(
mix_host: String,
identity_key: String,
sphinx_key: String,
version: String,
location: String,
layer: u64,
incentives_address: Option<String>,
) -> Self {
MixRegistrationInfo {
node_info: NodeInfo {
mix_host,
identity_key,
sphinx_key,
version,
location,
incentives_address: incentives_address.unwrap_or_else(|| "".to_string()),
},
layer,
}
}
}
// actual entry in topology
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisteredMix {
#[serde(flatten)]
pub(crate) mix_info: MixRegistrationInfo,
pub(crate) registration_time: i64,
pub(crate) reputation: i64,
}
impl RegisteredMix {
pub fn identity(&self) -> String {
self.mix_info.node_info.identity_key.clone()
}
pub fn mix_host(&self) -> String {
self.mix_info.node_info.mix_host.clone()
}
}
impl TryInto<topology::mix::Node> for RegisteredMix {
type Error = ConversionError;
fn try_into(self) -> Result<topology::mix::Node, Self::Error> {
let resolved_hostname = self
.mix_info
.node_info
.mix_host
.to_socket_addrs()
.map_err(ConversionError::InvalidAddress)?
.next()
.ok_or_else(|| {
ConversionError::InvalidAddress(io::Error::new(
io::ErrorKind::Other,
"no valid socket address",
))
})?;
Ok(topology::mix::Node {
location: self.mix_info.node_info.location,
host: resolved_hostname,
identity_key: identity::PublicKey::from_base58_string(
self.mix_info.node_info.identity_key,
)?,
sphinx_key: encryption::PublicKey::from_base58_string(
self.mix_info.node_info.sphinx_key,
)?,
layer: self.mix_info.layer,
registration_time: self.registration_time,
reputation: self.reputation,
version: self.mix_info.node_info.version,
})
}
}
@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use iron::prelude::*;
use iron::status;
pub mod gateway;
pub mod mixmining;
pub mod mixnode;
mod node;
pub mod topology;
pub mod validators;
@@ -12,8 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use built;
use serde::{Deserialize, Serialize};
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct NodeInfo {
pub(crate) mix_host: String,
pub(crate) identity_key: String,
pub(crate) sphinx_key: String,
pub(crate) version: String,
pub(crate) location: String,
pub(crate) incentives_address: String,
}
@@ -0,0 +1,74 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::models::gateway::RegisteredGateway;
use crate::models::mixnode::RegisteredMix;
use crate::models::validators::ValidatorsOutput;
use log::*;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use topology::{MixLayer, NymTopology};
// Topology shows us the current state of the overall Nym network
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Topology {
pub mix_nodes: Vec<RegisteredMix>,
pub gateways: Vec<RegisteredGateway>,
pub validators: ValidatorsOutput,
}
// changed from `TryInto`. reason being is that we should not fail entire topology
// conversion if there's one invalid node on the network screwing around
impl Into<NymTopology> for Topology {
fn into(self) -> NymTopology {
use std::collections::HashMap;
let mut mixes = HashMap::new();
for mix in self.mix_nodes.into_iter() {
let layer = mix.mix_info.layer as MixLayer;
if layer == 0 || layer > 3 {
warn!(
"{} says it's on invalid layer {}!",
mix.mix_info.node_info.identity_key, layer
);
continue;
}
let mix_id = mix.mix_info.node_info.identity_key.clone();
let layer_entry = mixes.entry(layer).or_insert_with(Vec::new);
match mix.try_into() {
Ok(mix) => layer_entry.push(mix),
Err(err) => {
warn!("Mix {} is malformed - {:?}", mix_id, err);
continue;
}
}
}
let mut gateways = Vec::with_capacity(self.gateways.len());
for gate in self.gateways.into_iter() {
let gate_id = gate.gateway_info.node_info.identity_key.clone();
match gate.try_into() {
Ok(gate) => gateways.push(gate),
Err(err) => {
warn!("Gateway {} is malformed - {:?}", gate_id, err);
continue;
}
}
}
NymTopology::new(mixes, gateways)
}
}
@@ -15,19 +15,15 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderPresence {
pub location: String,
pub client_listener: String,
pub mixnet_listener: String,
pub pub_key: String,
pub registered_clients: Vec<MixProviderClient>,
pub last_seen: u64,
pub version: String,
pub struct ValidatorsOutput {
pub(crate) block_height: i64,
pub(crate) validators: Vec<ValidatorOutput>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MixProviderClient {
pub pub_key: String,
pub(crate) struct ValidatorOutput {
pub(crate) address: String,
pub(crate) pub_key: String,
pub(crate) proposer_priority: i64,
pub(crate) voting_power: i64,
}
@@ -0,0 +1,54 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::models::topology::Topology;
use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError};
use crate::ErrorResponse;
use reqwest::{Method, Url};
use serde::Deserialize;
pub struct Request {
url: Url,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub(crate) enum Response {
Ok(Topology),
Error(ErrorResponse),
}
impl RESTRequest for Request {
const METHOD: Method = Method::GET;
const RELATIVE_PATH: &'static str = "/api/mixmining/topology/active";
type JsonPayload = ();
type ExpectedJsonResponse = Response;
fn new(
base_url: &str,
_: Option<Vec<PathParam>>,
_: Option<Vec<QueryParam>>,
_: Option<Self::JsonPayload>,
) -> Result<Self, RESTRequestError> {
let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH))
.map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?;
Ok(Request { url })
}
fn url(&self) -> &Url {
&self.url
}
}
@@ -0,0 +1,52 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::models::gateway::GatewayRegistrationInfo;
use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError};
use crate::DefaultRESTResponse;
use reqwest::{Method, Url};
pub struct Request {
url: Url,
payload: GatewayRegistrationInfo,
}
impl RESTRequest for Request {
const METHOD: Method = Method::POST;
const RELATIVE_PATH: &'static str = "/api/mixmining/register/gateway";
type JsonPayload = GatewayRegistrationInfo;
type ExpectedJsonResponse = DefaultRESTResponse;
fn new(
base_url: &str,
_: Option<Vec<PathParam>>,
_: Option<Vec<QueryParam>>,
body_payload: Option<Self::JsonPayload>,
) -> Result<Self, RESTRequestError> {
let payload = body_payload.ok_or_else(|| RESTRequestError::NoPayloadProvided)?;
let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH))
.map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?;
Ok(Request { url, payload })
}
fn url(&self) -> &Url {
&self.url
}
fn json_payload(&self) -> Option<&Self::JsonPayload> {
Some(&self.payload)
}
}

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