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:
committed by
GitHub
parent
d05b313a99
commit
2fa5c3c3b7
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+10
-5
@@ -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"]
|
||||
@@ -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.
|
||||
|
||||
[](https://travis-ci.com/nymtech/nym)
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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,
|
||||
},
|
||||
|
||||
+12
-13
@@ -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) {
|
||||
|
||||
+20
-6
@@ -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(),
|
||||
|
||||
+7
-7
@@ -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")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,3 +1,2 @@
|
||||
pub mod built_info;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
|
||||
@@ -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
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"));
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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"));
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
@@ -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",
|
||||
|
||||
+8297
-3385
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
@@ -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();
|
||||
|
||||
@@ -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"));
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(_))
|
||||
}
|
||||
}
|
||||
|
||||
+2
-10
@@ -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(),
|
||||
}
|
||||
}
|
||||
+6
@@ -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,
|
||||
}
|
||||
-1
@@ -13,4 +13,3 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub mod metrics;
|
||||
pub mod presence;
|
||||
+6
-13
@@ -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)]
|
||||
+15
-25
@@ -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()
|
||||
}
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -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;
|
||||
+10
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
+8
-12
@@ -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
Reference in New Issue
Block a user