a7d8613c9d
* Feature/multi surbs (#1796) * bunch of wip with focus on serialization * Being able to send normal data (NO SURBS yet) to yourself again * Fixed RepliableMessage deserialization * Recovering data from surb messages * Extracted common code in sphinx payload construction * Cleanup within received buffer * requesting, sending and using additional reply surbs * Following discussion with @simonwicky, removing sender proof and decreasing size of sender tag * Made sender tag more easily configurable * Refactoring of message creation * Propagating reply surb acks but not retransmitting them yet * Surb retransmissions * requesting additional surbs from the retransmission flow * correctly determining the point of requesting additional surbs * Ability to use socks5 (and network requester) with surbs * Improved surbs retranmsission reliability * naive way of not over-requesting surbs * wip on tag storage * Improved error propagation for message construction * Requesting more surbs for stale entries * Better controlling the point of having to request additional surbs * Using pseudorandom sender tag instead of a hardcoded one * First cleanup round in MessageHandler * Error cleanup and if simplification * Assigned a more permanent name to the ReplyController * Removed PendingReply redundant type * Made socks5 client less eager to over-send reply surbs * 'anonymous' field on socks5 client to decide whether to use surbs or attach address * Dead code and import removal in client-core * Updating ClientRequest variants * Adjusted decision threshold for requesting more surbs * Native client cleanup * Made socks5 client usage of surbs configurable * Restored statistics in network requester * Validator-api compiles once again * Further improved surb request logic * boxing the recipient in controller requests * Removal of hardcoded values in favour of propagating them from the config * more validation during surb requests * Fixed ClientRequest::Send deserialization * Added length checks for request deserialization * post-merge formatting * Unit tests once again compile and pass * controlling retransmission_reply_surb_request_size from config * More Recipient boxing action * Requesting additional reply surbs for retransmission BEFORE dipping below the threshold * Making clippy generally happier * Wasm client compiles (but might not yet work correctly) * Feature/use expect instead of panicking (#1797) * Implementation of 'Debug' on 'RealMessage' * expect with failed channel name instead of throwing empty panics * Introduced Debug trait constraint in ProxyRunner * Derive Debug for socks5_requests::Message * Fix decrypting stored received msg (#1786) * Fix decrypting stored received msg * rustfmt * Moving binary message recovery to separate function Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * real_traffic_stream: reduce frequency of status print (#1794) * Properly defined unnamed errors * Dealing with previously ignored errors * logging improvements * Removed old example code Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> * Missing changelog entry for multi-surbs (#1802) * Making anonymous sender tag human readable (#1801) * Created wrapper with string serialization for AnonymousSenderTag * Using Display implementation of AnonymousSenderTag for logs * Using Display implementation of MessageRecoveryError when logging (#1803) * Using Display implementation of MessageRecoveryError when logging * Updated changelog * Defined socks5 client startup flag to enable reply-surb communication (#1804) * Feature/persistent surbs data (#1835) * prototyping wip * Implemented ReplyStorageBackend trait for the sql-backed storage * Storing correct surb threshold * using correct database path * Starting surb persistent storage in native and socks5 clients * loading or creating fresh surb storage in socks5 and native clients * making clippy happier + fixing config templates * Creating status table on database rotation * Completed the 'Empty' ReplyStorageBackend * feature locking wasm-incompatible bits and pieces * Feature/develop resync (#1844) * Network-requester: throttle inbound connections (#1789) * Return and handle ClientRequest::LaneQueueLenghts * Pass lane queue lengths to inbound future * Remove unused self reference * Request lane queue lengths periodically for all open connections * Add timeouts * Rename to ConnectionCommandSender and Receiver * Rename to client_connection_tx/rx * Fix wasm build * Replace bool with enum * rust: bump required version to 1.65 in some crates that need it * Add step to release GH actions (#1792) * feat: add a release step to nym contracts GH action * feat: add shrinking the size of wasm * Possibilty to change gateway ws listener (#1779) * add: set gatewayListener * Update types.ts * Update worker.ts * Update contracts-build.yml * real_traffic_stream: reduce frequency of status print (#1794) * Update wallet and connect lock files (#1793) * client-core: add warning when delay multiplier is larger than 1 * Fix decrypting stored received msg (#1786) * Fix decrypting stored received msg * rustfmt * Moving binary message recovery to separate function Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * Feature/use expect instead of panicking (#1797) * Implementation of 'Debug' on 'RealMessage' * expect with failed channel name instead of throwing empty panics * Introduced Debug trait constraint in ProxyRunner * Derive Debug for socks5_requests::Message * Make connection_id optional in ClientRequest::Send (#1798) * changelog: add missing entry for fixing message decrypt in gateway-client * websocket-requests: fix length check before deserialize (#1799) * Fix export dkg contract addr (#1800) * Export dkg contract for mainnet when no config file present * Remove redundant env files * nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client * Feature/gateway client protocol version (#1795) * Introducing concept of gateway protocol version * Remove version-based gateway filtering * Fixed the unit test * grammar * Set build on latest release on schedule event * Added nightly build workflow on second latest release * socks5: if any task panics, signal all other tasks to shutdown (#1805) * socks5: signal shutdown on error * Mark as success * Tidy * Reduce wait to 5 sec * Replace unwrap with expect * Two more unwraps * Update changelog * client-core: less frequent status logging (#1806) * Feature/nym connect UI updates (#1784) * create custom titlebar * create help page * create generic modal component * create separate connection time component * link to shipyard docs * move timer to separate component and update connection status component usage * use separate component for copying ip and port details * only show infomodal once after connection * set service provider on tauri side * Emit events when stopped * listen and unlisten for tauri events * connect: add trace log to get_services * Add back CI notifications * Update README Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> * Use default serde value for upgrade (#1807) * fix ui overflow bug (#1808) * update nym connect error text (#1809) * set flag to false * Fix wait_for_signal_and_error on win (#1811) * Add socks5-client changes to nym-connect changelog * Fix links in nym-connect changelog * More entries in nym-connect CHANGELOG * Fix typo in changelog * Update CHANGELOG.md * Experiment/client refactoring (#1814) * experimenting with extracting more common client code * drying up the wasm client * allowing some dead code for the time being * fixed formatting in nym-connect * made socks5 client inside nym-connect immutable * made clippy a bit happier * hidden away target locking for recv timeout * New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Bumping version numbers * Changelog for v1.1.1 * Bumping final version numbers for 1.1.1 * Bumping nym-cli version, missed it last time * socks5-client: SOCKS4a support (#1822) * socks5-client: SOCKS4a support * Tidy * Fix a few errors in socks5 client and network-requester (#1823) * Fix two unwraps in socks5 and network-requester * Make sure client task never sends shutdown signal * Fix panic on getting socks version * wip * connecting to the back and making the requests work * display details modal * logs removal * Feature/pledge more (#1679) * New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Changelog update * nym-connect: update lock file * avoid mix tokens pools * amount error * envs/mainnet: update to latest mixnet contract and nymd validator url * validator-api: add missing shortform for --config-env-file (#1830) * gateway-client: handle shutdown listener (#1829) * WIP * WIP: try another approach * WIP * Reworked * Tidy * fix * validator-api: remove storage dependency in contract cache (#1685) * validator-api: remove storage dependency in contract cache * validator-client: update detailed routes * contract_cache: forward to new endpoints for compat * Move reward_estimate * client: add --no-cover and update --fastmode (#1831) * adding a oversaturaded bonding more modal * common/task: extract out spawn_with_report_error (#1837) * stop panic on failed buffer request * Compilable wasm client * Enabled hard error on lack of gateway-client protocol version * Missing generic parameter for ClientCoreError in BackendError * Removed unused imports * Additional wasm feature locking Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> Co-authored-by: Fran Arbanas <arbanasfran@gmail.com> Co-authored-by: cgi-bin/ <6095048+sven-hash@users.noreply.github.com> Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net> Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com> Co-authored-by: Raphaël Walther <raphael@nymtech.net> Co-authored-by: Fouad <fmtabbara@hotmail.co.uk> Co-authored-by: Gala <calero.vg@gmail.com> Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com> * Making native client wait for shutdown * Marking dead test code * Feature/multi surbs invalidation (#1858) * Cleaned up RealMessagesController constructor * introduced config field for maximum_reply_surb_age * Handling edge-case reply-surb failures * invalidating old reply surbs * Removing old reply keys from cache * Invalidating old reply keys * missing config changes * logging created tag details * Fixed clippy warning in test code * Saving reply key timestamp on data flush (#1867) * Remove panic if ReconstructedMessagesReceiver is closed (#1868) Instead log error and return because presumably the shutdown procedure has started * Feature/multi surbs basic wasm interface (#1846) * Added builder to wasm client * missing wasm_bindgen macros * Added constructor macro on GatewayEndpointConfig * Attempting to use updated wasm client api * Removing dead code * Exposed other messages types in wasm client * cleanup in js-example * Changed 'self_address' to be a method call * Removed needless borrow when cloning an Arc * Improving arguments in 'on_message' callback * fixed wasm-client dependency/features * Reverted hard requirement for gateway protocol presence (#1875) * Feature/prioritise surb retransmission (#1883) * Improved error messages + removed redundant variants * Improved estimation of 'expected_forward_delay' * Removed old wasm-specific startup code * Removed old unused reply-related code * hacky and temporary way of buffering retransmission data * offloading retransmission reply handling to ReplyController * fixed linter errors + rebuffering retransmission data on failure * Removed unused fields from wasm client debug config * Chore/v1.2.0 update (#2666) * Network-requester: throttle inbound connections (#1789) * Return and handle ClientRequest::LaneQueueLenghts * Pass lane queue lengths to inbound future * Remove unused self reference * Request lane queue lengths periodically for all open connections * Add timeouts * Rename to ConnectionCommandSender and Receiver * Rename to client_connection_tx/rx * Fix wasm build * Replace bool with enum * rust: bump required version to 1.65 in some crates that need it * Add step to release GH actions (#1792) * feat: add a release step to nym contracts GH action * feat: add shrinking the size of wasm * Possibilty to change gateway ws listener (#1779) * add: set gatewayListener * Update types.ts * Update worker.ts * Update contracts-build.yml * real_traffic_stream: reduce frequency of status print (#1794) * Update wallet and connect lock files (#1793) * client-core: add warning when delay multiplier is larger than 1 * Fix decrypting stored received msg (#1786) * Fix decrypting stored received msg * rustfmt * Moving binary message recovery to separate function Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * Feature/use expect instead of panicking (#1797) * Implementation of 'Debug' on 'RealMessage' * expect with failed channel name instead of throwing empty panics * Introduced Debug trait constraint in ProxyRunner * Derive Debug for socks5_requests::Message * Make connection_id optional in ClientRequest::Send (#1798) * changelog: add missing entry for fixing message decrypt in gateway-client * websocket-requests: fix length check before deserialize (#1799) * Fix export dkg contract addr (#1800) * Export dkg contract for mainnet when no config file present * Remove redundant env files * nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client * Feature/gateway client protocol version (#1795) * Introducing concept of gateway protocol version * Remove version-based gateway filtering * Fixed the unit test * grammar * Set build on latest release on schedule event * feat(wallet): buy page bootstrap * feat(wallet-buy): tutorial * feat(explorer-api): add route to fetch nym terms&cdts * Revert "feat(explorer-api): add route to fetch nym terms&cdts" This reverts commit 876f752697d89061b1904e1ddd1d5bcb7045dc5c. * feat(wallet-buy-nym): buy page new ui * fix(wallet-buy-nym): signature output * feat(wallet-buy-nym): update signature modal ui * Added nightly build workflow on second latest release * socks5: if any task panics, signal all other tasks to shutdown (#1805) * socks5: signal shutdown on error * Mark as success * Tidy * Reduce wait to 5 sec * Replace unwrap with expect * Two more unwraps * Update changelog * client-core: less frequent status logging (#1806) * Feature/nym connect UI updates (#1784) * create custom titlebar * create help page * create generic modal component * create separate connection time component * link to shipyard docs * move timer to separate component and update connection status component usage * use separate component for copying ip and port details * only show infomodal once after connection * set service provider on tauri side * Emit events when stopped * listen and unlisten for tauri events * connect: add trace log to get_services * Add back CI notifications * Update README Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> * Use default serde value for upgrade (#1807) * fix ui overflow bug (#1808) * feat(wallet): add link to nym exchange interface * update nym connect error text (#1809) * refactor(wallet): clean code * set flag to false * Fix wait_for_signal_and_error on win (#1811) * Use config URLs in clients before the env values (#1813) * Add socks5-client changes to nym-connect changelog * Fix links in nym-connect changelog * More entries in nym-connect CHANGELOG * Fix typo in changelog * Update CHANGELOG.md * Experiment/client refactoring (#1814) * experimenting with extracting more common client code * drying up the wasm client * allowing some dead code for the time being * fixed formatting in nym-connect * made socks5 client inside nym-connect immutable * made clippy a bit happier * hidden away target locking for recv timeout * New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Feature/dkg integration tests (#1815) * DKG contract e2e test * Refactor to the same format as other contracts * Vk share tests * State tests * Dealings tests * Dealer tests * Api dkg tests * Fix path to contract after refactor * Fix test target clippy * Bumping version numbers * Changelog for v1.1.1 * Bumping final version numbers for 1.1.1 * Bumping nym-cli version, missed it last time * socks5-client: SOCKS4a support (#1822) * socks5-client: SOCKS4a support * Tidy * Fix a few errors in socks5 client and network-requester (#1823) * Fix two unwraps in socks5 and network-requester * Make sure client task never sends shutdown signal * Fix panic on getting socks version * wip * connecting to the back and making the requests work * display details modal * logs removal * Feature/pledge more (#1679) * New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Changelog update * Feature/pledge more (#1679) * New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Changelog update * Fix a few errors in socks5 client and network-requester (backport) (#1824) * Fix two unwraps in socks5 and network-requester * Make sure client task never sends shutdown signal * nym-connect: update lock file * fix(wallet): typo * avoid mix tokens pools * fix(wallet): typo * fix(wallet): buy tutorial ui responsivness * amount error * envs/mainnet: update to latest mixnet contract and nymd validator url * validator-api: add missing shortform for --config-env-file (#1830) * gateway-client: handle shutdown listener (#1829) * WIP * WIP: try another approach * WIP * Reworked * Tidy * fix * validator-api: remove storage dependency in contract cache (#1685) * validator-api: remove storage dependency in contract cache * validator-client: update detailed routes * contract_cache: forward to new endpoints for compat * Move reward_estimate * Node family management (#1670) * Family management messages * Add family queries * Add queries to client * Layer assignment message * Paged family queries, annotate mixnodes with family * Add layer assignments to epoch operations * Remove family layer peristence * Add NotImplemented error for kick Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * Fixed layer distribution skewness check (#1766) * client: add --no-cover and update --fastmode (#1831) * adding a oversaturaded bonding more modal * Use better naming on gateway credential handling (#1834) * Fix comment in configuration file (#1836) * common/task: extract out spawn_with_report_error (#1837) * nym-connect/changelog: add note about disconnect fix * Feature/simplify credential binary (#1841) * Expose name of standard directories * Use one command instead of two * nym-connect: append error to failed message (#1839) * nym-connect: append error to failed message * changelog: add note * Fix clippy * remove extra checks to display vesting schedule(#1826) * Set explorer to use rpc.nymtech.net * update versions for platfrom, nym-connect and nym-wallet to v1.1.2 * changed nym-connect version to 1.1.1 * Modifying changelog for v1.1.2 * changed nym-connect version to 1.1.2 * update nym-connect CHANGELOG * Updated changelog for wallet * Feature/wallet content updates (#1825) * fix up balance screen * fix up app bar and nym logo alignment * fix up delegation action icon font weight * fix up bond page * Corrected env variable name in workflows * Use config URLs in clients before the env values (#1813) * Feature/dkg integration tests (#1815) * DKG contract e2e test * Refactor to the same format as other contracts * Vk share tests * State tests * Dealings tests * Dealer tests * Api dkg tests * Fix path to contract after refactor * Fix test target clippy * Node family management (#1670) * Family management messages * Add family queries * Add queries to client * Layer assignment message * Paged family queries, annotate mixnodes with family * Add layer assignments to epoch operations * Remove family layer peristence * Add NotImplemented error for kick Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * Fixed layer distribution skewness check (#1766) * Use better naming on gateway credential handling (#1834) * Fix comment in configuration file (#1836) * nym-connect/changelog: add note about disconnect fix * Feature/simplify credential binary (#1841) * Expose name of standard directories * Use one command instead of two * Fix clippy * feat(wallet): buy page bootstrap * feat(wallet-buy): tutorial * feat(explorer-api): add route to fetch nym terms&cdts * Revert "feat(explorer-api): add route to fetch nym terms&cdts" This reverts commit 876f752697d89061b1904e1ddd1d5bcb7045dc5c. * feat(wallet-buy-nym): buy page new ui * fix(wallet-buy-nym): signature output * feat(wallet-buy-nym): update signature modal ui * feat(wallet): add link to nym exchange interface * refactor(wallet): clean code * fix(wallet): typo * fix(wallet): typo * fix(wallet): buy tutorial ui responsivness * update versions for platfrom, nym-connect and nym-wallet to v1.1.2 * changed nym-connect version to 1.1.1 * Modifying changelog for v1.1.2 * changed nym-connect version to 1.1.2 * update nym-connect CHANGELOG * Updated changelog for wallet * Resolve merge conflicts * Update qa-qwerty.env * Fixed URL to branch * changed ubuntu-latest on GH actions to ubuntu-20.04 * docs: updated changelog for contracts release v1.1.2 and updated versions of mixnet and vesting contracts as well * Add ignore to dkg expensive tests (#1856) * introduce minimize button in custom title bar (#1843) * refresh balance after sending tokens (#1857) * Feature/fix client multi cred consume (#1859) * Mark consumed credentials in the db * Add signature log * Fix wasm mock Storage trait * Fix clippy * Feature/verify bte proof (#1866) * Update lock file * Include bte public key verification * Wallet - Buy, copy changes (#1855) * use mix_id for account to get correct pending cost event (#1869) * use mix_id for account to get correct pending cost event * Properly add consumed to table (#1870) * nym-connect: update Cargo.lock to 1.1.2 * Clients: save init results to JSON (#1865) * clients: output results of init to json * Remove leftover dbg * Tidy * Fix nym-connect * Client: dedup setup gateway during init (#1871) * clients: dedup gateway setup logic * nym-connect: extract out print_save_config * Feature/dkg state to disk (#1872) * Add PersistentState * Save and load state to/from disk * If in progress, don't continually write the same state * Fix tests and add serde one * Update changelog * Fix clippy * network-requester: return error on socket close (#1876) * network-requester: return error when the socket closes * changelog: add note * clients: further deduplicate init code (#1873) * client-core: move init helpers to module * WIP * socks5: return error instead of terminate in init * Extract out reuse_existing_gateway_config * rustfmt * Remove comment out code * nym-connect: use setup_gateway * Linebreak * changelog: update * Tweak log * rustfmt * client: pick from old lanes probabilisticlly (#1877) * Pick from old lanes probabilisticly * changelog: update * clients: dont panic in base client gateway client handling (#1878) * client-core: fix some panics related to gateway-client * changelog: update * fix * changelog: fix wording * Use default mainnet values when nothing is specified (#1884) Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> Co-authored-by: Fran Arbanas <arbanasfran@gmail.com> Co-authored-by: cgi-bin/ <6095048+sven-hash@users.noreply.github.com> Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net> Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com> Co-authored-by: Raphaël Walther <raphael@nymtech.net> Co-authored-by: pierre <dommerc.pierre@gmail.com> Co-authored-by: Fouad <fmtabbara@hotmail.co.uk> Co-authored-by: Gala <calero.vg@gmail.com> Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com> Co-authored-by: Drazen Urch <drazen@urch.eu> Co-authored-by: durch <durch@users.noreply.github.com> Co-authored-by: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com> Co-authored-by: Fran Arbanas <arbanasfran@gmail.com> Co-authored-by: cgi-bin/ <6095048+sven-hash@users.noreply.github.com> Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net> Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com> Co-authored-by: Raphaël Walther <raphael@nymtech.net> Co-authored-by: Fouad <fmtabbara@hotmail.co.uk> Co-authored-by: Gala <calero.vg@gmail.com> Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com> Co-authored-by: pierre <dommerc.pierre@gmail.com> Co-authored-by: Drazen Urch <drazen@urch.eu> Co-authored-by: durch <durch@users.noreply.github.com> Co-authored-by: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com>
782 lines
30 KiB
Rust
782 lines
30 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::bandwidth::BandwidthController;
|
|
use crate::error::GatewayClientError;
|
|
use crate::packet_router::PacketRouter;
|
|
pub use crate::packet_router::{
|
|
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
|
};
|
|
use crate::socket_state::{PartiallyDelegated, SocketState};
|
|
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
|
use crypto::asymmetric::identity;
|
|
use futures::{SinkExt, StreamExt};
|
|
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
|
use gateway_requests::iv::IV;
|
|
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
|
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
|
|
use log::*;
|
|
use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
|
use nymsphinx::forwarding::packet::MixPacket;
|
|
use rand::rngs::OsRng;
|
|
use std::convert::TryFrom;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use task::ShutdownListener;
|
|
use tungstenite::protocol::Message;
|
|
|
|
#[cfg(feature = "coconut")]
|
|
use coconut_interface::Credential;
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use credential_storage::PersistentStorage;
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use tokio_tungstenite::connect_async;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use crate::wasm_storage::PersistentStorage;
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_timer;
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_utils::websocket::JSWebsocket;
|
|
|
|
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
|
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
|
|
|
pub struct GatewayClient {
|
|
authenticated: bool,
|
|
disabled_credentials_mode: bool,
|
|
bandwidth_remaining: i64,
|
|
gateway_address: String,
|
|
gateway_identity: identity::PublicKey,
|
|
gateway_owner: String,
|
|
local_identity: Arc<identity::KeyPair>,
|
|
shared_key: Option<Arc<SharedKeys>>,
|
|
connection: SocketState,
|
|
packet_router: PacketRouter,
|
|
response_timeout_duration: Duration,
|
|
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
|
|
|
|
// reconnection related variables
|
|
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
|
should_reconnect_on_failure: bool,
|
|
/// Specifies maximum number of attempts client will try to reconnect to gateway on failure
|
|
/// before giving up.
|
|
reconnection_attempts: usize,
|
|
/// Delay between each subsequent reconnection attempt.
|
|
reconnection_backoff: Duration,
|
|
|
|
/// Listen to shutdown messages.
|
|
shutdown: ShutdownListener,
|
|
}
|
|
|
|
impl GatewayClient {
|
|
// TODO: put it all in a Config struct
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
gateway_address: String,
|
|
local_identity: Arc<identity::KeyPair>,
|
|
gateway_identity: identity::PublicKey,
|
|
gateway_owner: String,
|
|
shared_key: Option<Arc<SharedKeys>>,
|
|
mixnet_message_sender: MixnetMessageSender,
|
|
ack_sender: AcknowledgementSender,
|
|
response_timeout_duration: Duration,
|
|
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
|
|
shutdown: ShutdownListener,
|
|
) -> Self {
|
|
GatewayClient {
|
|
authenticated: false,
|
|
disabled_credentials_mode: true,
|
|
bandwidth_remaining: 0,
|
|
gateway_address,
|
|
gateway_identity,
|
|
gateway_owner,
|
|
local_identity,
|
|
shared_key,
|
|
connection: SocketState::NotConnected,
|
|
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender, shutdown.clone()),
|
|
response_timeout_duration,
|
|
bandwidth_controller,
|
|
should_reconnect_on_failure: true,
|
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
|
shutdown,
|
|
}
|
|
}
|
|
|
|
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
|
|
self.disabled_credentials_mode = disabled_credentials_mode;
|
|
}
|
|
|
|
// TODO: later convert into proper builder methods
|
|
pub fn with_reconnection_on_failure(&mut self, should_reconnect_on_failure: bool) {
|
|
self.should_reconnect_on_failure = should_reconnect_on_failure
|
|
}
|
|
|
|
pub fn with_reconnection_attempts(&mut self, reconnection_attempts: usize) {
|
|
self.reconnection_attempts = reconnection_attempts
|
|
}
|
|
|
|
pub fn with_reconnection_backoff(&mut self, backoff: Duration) {
|
|
self.reconnection_backoff = backoff
|
|
}
|
|
|
|
pub fn new_init(
|
|
gateway_address: String,
|
|
gateway_identity: identity::PublicKey,
|
|
gateway_owner: String,
|
|
local_identity: Arc<identity::KeyPair>,
|
|
response_timeout_duration: Duration,
|
|
) -> Self {
|
|
use futures::channel::mpsc;
|
|
|
|
// note: this packet_router is completely invalid in normal circumstances, but "works"
|
|
// perfectly fine here, because it's not meant to be used
|
|
let (ack_tx, _) = mpsc::unbounded();
|
|
let (mix_tx, _) = mpsc::unbounded();
|
|
let shutdown = ShutdownListener::dummy();
|
|
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
|
|
|
|
GatewayClient {
|
|
authenticated: false,
|
|
disabled_credentials_mode: true,
|
|
bandwidth_remaining: 0,
|
|
gateway_address,
|
|
gateway_identity,
|
|
gateway_owner,
|
|
local_identity,
|
|
shared_key: None,
|
|
connection: SocketState::NotConnected,
|
|
packet_router,
|
|
response_timeout_duration,
|
|
bandwidth_controller: None,
|
|
should_reconnect_on_failure: false,
|
|
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
|
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
|
shutdown,
|
|
}
|
|
}
|
|
|
|
pub fn gateway_identity(&self) -> identity::PublicKey {
|
|
self.gateway_identity
|
|
}
|
|
|
|
pub fn remaining_bandwidth(&self) -> i64 {
|
|
self.bandwidth_remaining
|
|
}
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
|
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
|
|
SocketState::PartiallyDelegated(_) => {
|
|
unreachable!("this branch should have never been reached!")
|
|
}
|
|
_ => Ok(()), // no need to do anything in those cases
|
|
}
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
|
SocketState::Available(mut socket) => {
|
|
(*socket).close(None).await;
|
|
Ok(())
|
|
}
|
|
SocketState::PartiallyDelegated(_) => {
|
|
unreachable!("this branch should have never been reached!")
|
|
}
|
|
_ => Ok(()), // no need to do anything in those cases
|
|
}
|
|
}
|
|
|
|
pub async fn close_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
if self.connection.is_partially_delegated() {
|
|
self.recover_socket_connection().await?;
|
|
}
|
|
|
|
self._close_connection().await
|
|
}
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
let ws_stream = match connect_async(&self.gateway_address).await {
|
|
Ok((ws_stream, _)) => ws_stream,
|
|
Err(e) => return Err(GatewayClientError::NetworkError(e)),
|
|
};
|
|
|
|
self.connection = SocketState::Available(Box::new(ws_stream));
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
|
|
Ok(ws_stream) => ws_stream,
|
|
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
|
|
};
|
|
|
|
self.connection = SocketState::Available(Box::new(ws_stream));
|
|
Ok(())
|
|
}
|
|
|
|
// ignore the current socket state (with which we can't do much anyway)
|
|
// note: the caller MUST ensure that if the stream was delegated, the spawned
|
|
// future is finished.
|
|
async fn attempt_reconnection(&mut self) -> Result<(), GatewayClientError> {
|
|
info!("Attempting gateway reconnection...");
|
|
self.authenticated = false;
|
|
|
|
for i in 1..self.reconnection_attempts {
|
|
info!("attempt {}...", i);
|
|
if self.authenticate_and_start().await.is_ok() {
|
|
info!("managed to reconnect!");
|
|
return Ok(());
|
|
}
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
tokio::time::sleep(self.reconnection_backoff).await;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
if let Err(err) = wasm_timer::Delay::new(self.reconnection_backoff).await {
|
|
error!(
|
|
"the timer has gone away while in reconnection backoff! - {}",
|
|
err
|
|
);
|
|
}
|
|
}
|
|
|
|
// final attempt (done separately to be able to return a proper error)
|
|
info!("attempt {}", self.reconnection_attempts);
|
|
match self.authenticate_and_start().await {
|
|
Ok(_) => {
|
|
info!("managed to reconnect!");
|
|
Ok(())
|
|
}
|
|
Err(err) => {
|
|
error!(
|
|
"failed to reconnect after {} attempts",
|
|
self.reconnection_attempts
|
|
);
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
|
|
// we use the fact that all request responses are Message::Text and only pushed
|
|
// sphinx packets are Message::Binary
|
|
|
|
let conn = match self.connection {
|
|
SocketState::Available(ref mut conn) => conn,
|
|
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
|
};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let timeout = tokio::time::sleep(self.response_timeout_duration);
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
tokio::pin!(timeout);
|
|
|
|
// technically the `wasm_timer` also works outside wasm, but unless required,
|
|
// I really prefer to just stick to tokio
|
|
#[cfg(target_arch = "wasm32")]
|
|
let mut timeout = wasm_timer::Delay::new(self.response_timeout_duration);
|
|
|
|
loop {
|
|
tokio::select! {
|
|
_ = self.shutdown.recv() => {
|
|
log::trace!("GatewayClient control response: Received shutdown");
|
|
log::debug!("GatewayClient control response: Exiting");
|
|
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
|
|
}
|
|
_ = &mut timeout => {
|
|
break Err(GatewayClientError::Timeout);
|
|
}
|
|
msg = conn.next() => {
|
|
let ws_msg = match cleanup_socket_message(msg) {
|
|
Err(err) => break Err(err),
|
|
Ok(msg) => msg
|
|
};
|
|
match ws_msg {
|
|
Message::Binary(bin_msg) => {
|
|
// if we have established the shared key already, attempt to use it for decryption
|
|
// otherwise there's not much we can do apart from just routing what we have on hand
|
|
if let Some(shared_keys) = &self.shared_key {
|
|
if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_keys) {
|
|
if let Err(err) = self.packet_router.route_received(vec![plaintext]) {
|
|
log::warn!("Route received failed: {:?}", err);
|
|
}
|
|
}
|
|
} else if let Err(err) = self.packet_router.route_received(vec![bin_msg]) {
|
|
log::warn!("Route received failed: {:?}", err);
|
|
}
|
|
}
|
|
Message::Text(txt_msg) => {
|
|
break ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we want to send a message (with response), we need to have a full control over the socket,
|
|
// as we need to be able to write the request and read the subsequent response
|
|
async fn send_websocket_message(
|
|
&mut self,
|
|
msg: Message,
|
|
) -> Result<ServerResponse, GatewayClientError> {
|
|
let should_restart_mixnet_listener = if self.connection.is_partially_delegated() {
|
|
self.recover_socket_connection().await?;
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
let conn = match self.connection {
|
|
SocketState::Available(ref mut conn) => conn,
|
|
SocketState::NotConnected => return Err(GatewayClientError::ConnectionNotEstablished),
|
|
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
|
};
|
|
conn.send(msg).await?;
|
|
let response = self.read_control_response().await;
|
|
|
|
if should_restart_mixnet_listener {
|
|
self.start_listening_for_mixnet_messages()?;
|
|
}
|
|
response
|
|
}
|
|
|
|
async fn batch_send_websocket_messages_without_response(
|
|
&mut self,
|
|
messages: Vec<Message>,
|
|
) -> Result<(), GatewayClientError> {
|
|
match self.connection {
|
|
SocketState::Available(ref mut conn) => {
|
|
let stream_messages: Vec<_> = messages.into_iter().map(Ok).collect();
|
|
let mut send_stream = futures::stream::iter(stream_messages);
|
|
Ok(conn.send_all(&mut send_stream).await?)
|
|
}
|
|
SocketState::PartiallyDelegated(ref mut partially_delegated) => {
|
|
if let Err(err) = partially_delegated
|
|
.batch_send_without_response(messages)
|
|
.await
|
|
{
|
|
error!("failed to batch send messages - {err}...");
|
|
// we must ensure we do not leave the task still active
|
|
if let Err(err) = self.recover_socket_connection().await {
|
|
error!("... and the delegated stream has also errored out - {err}")
|
|
}
|
|
Err(err)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
SocketState::NotConnected => Err(GatewayClientError::ConnectionNotEstablished),
|
|
_ => Err(GatewayClientError::ConnectionInInvalidState),
|
|
}
|
|
}
|
|
|
|
async fn send_websocket_message_without_response(
|
|
&mut self,
|
|
msg: Message,
|
|
) -> Result<(), GatewayClientError> {
|
|
match self.connection {
|
|
SocketState::Available(ref mut conn) => Ok(conn.send(msg).await?),
|
|
SocketState::PartiallyDelegated(ref mut partially_delegated) => {
|
|
if let Err(err) = partially_delegated.send_without_response(msg).await {
|
|
error!("failed to send message without response - {err}...");
|
|
// we must ensure we do not leave the task still active
|
|
if let Err(err) = self.recover_socket_connection().await {
|
|
error!("... and the delegated stream has also errored out - {err}")
|
|
}
|
|
Err(err)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
SocketState::NotConnected => Err(GatewayClientError::ConnectionNotEstablished),
|
|
_ => Err(GatewayClientError::ConnectionInInvalidState),
|
|
}
|
|
}
|
|
|
|
fn check_gateway_protocol(
|
|
&self,
|
|
gateway_protocol: Option<u8>,
|
|
) -> Result<(), GatewayClientError> {
|
|
// right now there are no failure cases here, but this might change in the future
|
|
match gateway_protocol {
|
|
None => {
|
|
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
|
// note: in +1.2.0 we will have to return a hard error here
|
|
Ok(())
|
|
}
|
|
Some(v) if v != PROTOCOL_VERSION => {
|
|
let err = GatewayClientError::IncompatibleProtocol {
|
|
gateway: Some(v),
|
|
current: PROTOCOL_VERSION,
|
|
};
|
|
error!("{err}");
|
|
Err(err)
|
|
}
|
|
|
|
Some(_) => {
|
|
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn register(&mut self) -> Result<(), GatewayClientError> {
|
|
if !self.connection.is_established() {
|
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
}
|
|
|
|
debug_assert!(self.connection.is_available());
|
|
|
|
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
|
// and putting it into the GatewayClient struct would be a hassle
|
|
let mut rng = OsRng;
|
|
|
|
let shared_key = match &mut self.connection {
|
|
SocketState::Available(ws_stream) => client_handshake(
|
|
&mut rng,
|
|
ws_stream,
|
|
self.local_identity.as_ref(),
|
|
self.gateway_identity,
|
|
)
|
|
.await
|
|
.map_err(GatewayClientError::RegistrationFailure),
|
|
_ => unreachable!(),
|
|
}?;
|
|
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
|
|
ServerResponse::Register {
|
|
protocol_version,
|
|
status,
|
|
} => (status, protocol_version),
|
|
ServerResponse::Error { message } => {
|
|
return Err(GatewayClientError::GatewayError(message))
|
|
}
|
|
_ => return Err(GatewayClientError::UnexpectedResponse),
|
|
};
|
|
|
|
self.check_gateway_protocol(gateway_protocol)?;
|
|
self.authenticated = authentication_status;
|
|
|
|
if self.authenticated {
|
|
self.shared_key = Some(Arc::new(shared_key));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn authenticate(
|
|
&mut self,
|
|
shared_key: Option<SharedKeys>,
|
|
) -> Result<(), GatewayClientError> {
|
|
if shared_key.is_none() && self.shared_key.is_none() {
|
|
return Err(GatewayClientError::NoSharedKeyAvailable);
|
|
}
|
|
if !self.connection.is_established() {
|
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
}
|
|
|
|
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
|
// and putting it into the GatewayClient struct would be a hassle
|
|
let mut rng = OsRng;
|
|
|
|
// because of the previous check one of the unwraps MUST succeed
|
|
let shared_key = shared_key
|
|
.as_ref()
|
|
.unwrap_or_else(|| self.shared_key.as_ref().unwrap());
|
|
let iv = IV::new_random(&mut rng);
|
|
let self_address = self
|
|
.local_identity
|
|
.as_ref()
|
|
.public_key()
|
|
.derive_destination_address();
|
|
let encrypted_address = EncryptedAddressBytes::new(&self_address, shared_key, &iv);
|
|
|
|
let msg =
|
|
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
|
|
|
|
match self.send_websocket_message(msg).await? {
|
|
ServerResponse::Authenticate {
|
|
protocol_version,
|
|
status,
|
|
bandwidth_remaining,
|
|
} => {
|
|
self.check_gateway_protocol(protocol_version)?;
|
|
self.authenticated = status;
|
|
self.bandwidth_remaining = bandwidth_remaining;
|
|
Ok(())
|
|
}
|
|
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
|
_ => Err(GatewayClientError::UnexpectedResponse),
|
|
}
|
|
}
|
|
|
|
/// Helper method to either call register or authenticate based on self.shared_key value
|
|
pub async fn perform_initial_authentication(
|
|
&mut self,
|
|
) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
|
if self.shared_key.is_some() {
|
|
self.authenticate(None).await?;
|
|
} else {
|
|
self.register().await?;
|
|
}
|
|
if self.authenticated {
|
|
// if we are authenticated it means we MUST have an associated shared_key
|
|
Ok(Arc::clone(self.shared_key.as_ref().unwrap()))
|
|
} else {
|
|
Err(GatewayClientError::AuthenticationFailure)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "coconut")]
|
|
async fn claim_coconut_bandwidth(
|
|
&mut self,
|
|
credential: Credential,
|
|
) -> Result<(), GatewayClientError> {
|
|
let mut rng = OsRng;
|
|
let iv = IV::new_random(&mut rng);
|
|
|
|
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
|
&credential,
|
|
self.shared_key.as_ref().unwrap(),
|
|
iv,
|
|
)
|
|
.into();
|
|
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
|
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
|
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
|
_ => Err(GatewayClientError::UnexpectedResponse),
|
|
}?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
|
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.into();
|
|
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
|
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
|
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
|
_ => Err(GatewayClientError::UnexpectedResponse),
|
|
}?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
|
if !self.authenticated {
|
|
return Err(GatewayClientError::NotAuthenticated);
|
|
}
|
|
if self.shared_key.is_none() {
|
|
return Err(GatewayClientError::NoSharedKeyAvailable);
|
|
}
|
|
if self.bandwidth_controller.is_none() && !self.disabled_credentials_mode {
|
|
return Err(GatewayClientError::NoBandwidthControllerAvailable);
|
|
}
|
|
|
|
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
|
|
if self.disabled_credentials_mode {
|
|
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
|
|
return self.try_claim_testnet_bandwidth().await;
|
|
}
|
|
|
|
let _gateway_owner = self.gateway_owner.clone();
|
|
|
|
#[cfg(feature = "coconut")]
|
|
let (credential, credential_id) = self
|
|
.bandwidth_controller
|
|
.as_ref()
|
|
.unwrap()
|
|
.prepare_coconut_credential()
|
|
.await?;
|
|
#[cfg(not(feature = "coconut"))]
|
|
return self.try_claim_testnet_bandwidth().await;
|
|
|
|
#[cfg(feature = "coconut")]
|
|
{
|
|
self.claim_coconut_bandwidth(credential).await?;
|
|
self.bandwidth_controller
|
|
.as_ref()
|
|
.unwrap()
|
|
.consume_credential(credential_id)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
|
packets
|
|
.iter()
|
|
.map(|packet| packet.sphinx_packet().len())
|
|
.sum::<usize>() as i64
|
|
}
|
|
|
|
pub async fn batch_send_mix_packets(
|
|
&mut self,
|
|
packets: Vec<MixPacket>,
|
|
) -> Result<(), GatewayClientError> {
|
|
if !self.authenticated {
|
|
return Err(GatewayClientError::NotAuthenticated);
|
|
}
|
|
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
|
return Err(GatewayClientError::NotEnoughBandwidth(
|
|
self.estimate_required_bandwidth(&packets),
|
|
self.bandwidth_remaining,
|
|
));
|
|
}
|
|
if !self.connection.is_established() {
|
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
}
|
|
|
|
let messages: Vec<_> = packets
|
|
.into_iter()
|
|
.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!"),
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
if let Err(err) = self
|
|
.batch_send_websocket_messages_without_response(messages)
|
|
.await
|
|
{
|
|
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
|
self.attempt_reconnection().await
|
|
} else {
|
|
Err(err)
|
|
}
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
async fn send_with_reconnection_on_failure(
|
|
&mut self,
|
|
msg: Message,
|
|
) -> Result<(), GatewayClientError> {
|
|
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
|
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
|
info!("Going to attempt a reconnection");
|
|
self.attempt_reconnection().await
|
|
} else {
|
|
Err(err)
|
|
}
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
|
|
if !self.connection.is_established() {
|
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
}
|
|
|
|
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
|
|
// so we don't need to include any here.
|
|
let msg = Message::Ping(Vec::new());
|
|
self.send_with_reconnection_on_failure(msg).await
|
|
}
|
|
|
|
// TODO: possibly make responses optional
|
|
pub async fn send_mix_packet(
|
|
&mut self,
|
|
mix_packet: MixPacket,
|
|
) -> Result<(), GatewayClientError> {
|
|
if !self.authenticated {
|
|
return Err(GatewayClientError::NotAuthenticated);
|
|
}
|
|
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
|
return Err(GatewayClientError::NotEnoughBandwidth(
|
|
mix_packet.sphinx_packet().len() as i64,
|
|
self.bandwidth_remaining,
|
|
));
|
|
}
|
|
if !self.connection.is_established() {
|
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
}
|
|
// 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(mix_packet).into_ws_message(
|
|
self.shared_key
|
|
.as_ref()
|
|
.expect("no shared key present even though we're authenticated!"),
|
|
);
|
|
self.send_with_reconnection_on_failure(msg).await
|
|
}
|
|
|
|
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
|
if self.connection.is_available() {
|
|
return Ok(());
|
|
}
|
|
if !self.connection.is_partially_delegated() {
|
|
return Err(GatewayClientError::ConnectionInInvalidState);
|
|
}
|
|
|
|
let conn = match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
|
SocketState::PartiallyDelegated(delegated_conn) => delegated_conn.merge().await?,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
self.connection = SocketState::Available(Box::new(conn));
|
|
Ok(())
|
|
}
|
|
|
|
// Note: this requires prior authentication
|
|
pub fn start_listening_for_mixnet_messages(&mut self) -> Result<(), GatewayClientError> {
|
|
if !self.authenticated {
|
|
return Err(GatewayClientError::NotAuthenticated);
|
|
}
|
|
if self.connection.is_partially_delegated() {
|
|
return Ok(());
|
|
}
|
|
if !self.connection.is_available() {
|
|
return Err(GatewayClientError::ConnectionInInvalidState);
|
|
}
|
|
|
|
let partially_delegated =
|
|
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
|
SocketState::Available(conn) => {
|
|
PartiallyDelegated::split_and_listen_for_mixnet_messages(
|
|
*conn,
|
|
self.packet_router.clone(),
|
|
Arc::clone(
|
|
self.shared_key
|
|
.as_ref()
|
|
.expect("no shared key present even though we're authenticated!"),
|
|
),
|
|
self.shutdown.clone(),
|
|
)
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
self.connection = SocketState::PartiallyDelegated(partially_delegated);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
|
if !self.connection.is_established() {
|
|
self.establish_connection().await?;
|
|
}
|
|
let shared_key = self.perform_initial_authentication().await?;
|
|
|
|
if self.bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD {
|
|
info!("Claiming more bandwidth for your tokens. This will use {} token(s) from your wallet. \
|
|
Stop the process now if you don't want that to happen.", TOKENS_TO_BURN);
|
|
self.claim_bandwidth().await?;
|
|
}
|
|
|
|
// this call is NON-blocking
|
|
self.start_listening_for_mixnet_messages()?;
|
|
|
|
Ok(shared_key)
|
|
}
|
|
}
|