diff --git a/.gitignore b/.gitignore index f102743734..b8df49d1a7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ validator-config *.patch validator-api-config.toml dist -storybook-static \ No newline at end of file +storybook-static +envs/qwerty.env \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4303004d95..11a2b387d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,7 @@ dependencies = [ "client-connections", "config", "crypto", + "dashmap 5.4.0", "dirs", "futures", "gateway-client", @@ -1350,6 +1351,19 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.4", +] + [[package]] name = "der" version = "0.5.1" @@ -2274,13 +2288,14 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hidapi" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d26e1151deaab68f34fbfd16d491a2a0170cf98d69d3efa23873b567a4199e1" +checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" dependencies = [ "cc", "libc", "pkg-config", + "winapi", ] [[package]] @@ -2669,7 +2684,7 @@ checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" dependencies = [ "arrayref", "no-std-compat", - "snafu 0.7.1", + "snafu 0.7.3", ] [[package]] @@ -2765,9 +2780,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3227,7 +3242,7 @@ dependencies = [ "config", "credentials", "crypto", - "dashmap", + "dashmap 4.0.2", "dirs", "dotenv", "futures", @@ -3541,6 +3556,7 @@ dependencies = [ "nymsphinx-types", "rand 0.7.3", "rand_distr", + "thiserror", "tokio", "topology", ] @@ -3566,6 +3582,7 @@ dependencies = [ "nymsphinx-types", "rand 0.7.3", "serde", + "thiserror", ] [[package]] @@ -3579,6 +3596,7 @@ dependencies = [ "nymsphinx-types", "rand 0.7.3", "serde", + "thiserror", "topology", ] @@ -3591,6 +3609,7 @@ dependencies = [ "nymsphinx-params", "nymsphinx-types", "rand 0.7.3", + "thiserror", ] [[package]] @@ -3656,9 +3675,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "oorandom" @@ -3760,7 +3779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.2", + "parking_lot_core 0.9.4", ] [[package]] @@ -3779,15 +3798,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec 1.8.0", - "windows-sys 0.34.0", + "windows-sys 0.42.0", ] [[package]] @@ -5237,12 +5256,12 @@ dependencies = [ [[package]] name = "snafu" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2" +checksum = "a152ba99b054b22972ee794cf04e5ef572da1229e33b65f3c57abbff0525a454" dependencies = [ "doc-comment", - "snafu-derive 0.7.1", + "snafu-derive 0.7.3", ] [[package]] @@ -5258,9 +5277,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5" +checksum = "d5e79cdebbabaebb06a9bdbaedc7f159b410461f63611d4d0e3fb0fab8fed850" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -5289,7 +5308,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859" +source = "git+https://github.com/nymtech/sphinx?rev=e05a1992522ed0afd3c6fcac160313ffc9bb306a#e05a1992522ed0afd3c6fcac160313ffc9bb306a" dependencies = [ "aes 0.7.5", "arrayref", @@ -5833,18 +5852,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -6116,6 +6135,7 @@ dependencies = [ "nymsphinx-addressing", "nymsphinx-types", "rand 0.7.3", + "thiserror", "version-checker", ] @@ -6818,19 +6838,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" -dependencies = [ - "windows_aarch64_msvc 0.34.0", - "windows_i686_gnu 0.34.0", - "windows_i686_msvc 0.34.0", - "windows_x86_64_gnu 0.34.0", - "windows_x86_64_msvc 0.34.0", -] - [[package]] name = "windows-sys" version = "0.36.1" @@ -6845,10 +6852,25 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_msvc" -version = "0.34.0" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" @@ -6857,10 +6879,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] -name = "windows_i686_gnu" -version = "0.34.0" +name = "windows_aarch64_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" @@ -6869,10 +6891,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] -name = "windows_i686_msvc" -version = "0.34.0" +name = "windows_i686_gnu" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" @@ -6881,10 +6903,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] -name = "windows_x86_64_gnu" -version = "0.34.0" +name = "windows_i686_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" @@ -6893,10 +6915,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] -name = "windows_x86_64_msvc" -version = "0.34.0" +name = "windows_x86_64_gnu" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" @@ -6904,6 +6932,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" version = "0.10.1" diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index e296a30591..d7ee74915d 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] dirs = "4.0" +dashmap = "5.4.0" futures = "0.3" humantime-serde = "1.0" log = "0.4" @@ -50,12 +51,14 @@ features = ["futures"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.task] path = "../../common/task" +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sled] +version = "0.34.7" + [dev-dependencies] tempfile = "3.1.0" [features] -default = ["reply-surb"] +default = [] wasm = ["gateway-client/wasm"] coconut = ["gateway-client/coconut", "gateway-requests/coconut"] -reply-surb = ["sled"] diff --git a/clients/client-core/src/client/cover_traffic_stream.rs b/clients/client-core/src/client/cover_traffic_stream.rs index ee3b7851cb..f0f5079f2b 100644 --- a/clients/client-core/src/client/cover_traffic_stream.rs +++ b/clients/client-core/src/client/cover_traffic_stream.rs @@ -151,15 +151,16 @@ impl LoopCoverTrafficStream { // poisson delay, but is it really a problem? let topology_permit = self.topology_access.get_read_permit().await; // the ack is sent back to ourselves (and then ignored) - let topology_ref_option = topology_permit.try_get_valid_topology_ref( + let topology_ref = match topology_permit.try_get_valid_topology_ref( &self.our_full_destination, Some(&self.our_full_destination), - ); - if topology_ref_option.is_none() { - warn!("No valid topology detected - won't send any loop cover message this time"); - return; - } - let topology_ref = topology_ref_option.unwrap(); + ) { + Ok(topology) => topology, + Err(err) => { + warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"); + return; + } + }; let cover_message = generate_loop_cover_packet( &mut self.rng, diff --git a/clients/client-core/src/client/inbound_messages.rs b/clients/client-core/src/client/inbound_messages.rs index 909e3720f9..6d62fb0473 100644 --- a/clients/client-core/src/client/inbound_messages.rs +++ b/clients/client-core/src/client/inbound_messages.rs @@ -1,40 +1,80 @@ use client_connections::TransmissionLane; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; pub type InputMessageSender = tokio::sync::mpsc::Sender; pub type InputMessageReceiver = tokio::sync::mpsc::Receiver; #[derive(Debug)] pub enum InputMessage { - Fresh { + /// The simplest message variant where no additional information is attached. + /// You're simply sending your `data` to specified `recipient` without any tagging. + /// + /// Ends up with `NymMessage::Plain` variant + Regular { recipient: Recipient, data: Vec, - with_reply_surb: bool, lane: TransmissionLane, }, + + /// Create a message used for a duplex anonymous communication where the recipient + /// will never learn of our true identity. This is achieved by carefully sending `reply_surbs`. + /// + /// Note that if reply_surbs is set to zero then + /// this variant requires the client having sent some reply_surbs in the past + /// (and thus the recipient also knowing our sender tag). + /// + /// Ends up with `NymMessage::Repliable` variant + Anonymous { + recipient: Recipient, + data: Vec, + reply_surbs: u32, + lane: TransmissionLane, + }, + + /// Attempt to use our internally received and stored `ReplySurb` to send the message back + /// to specified recipient whilst not knowing its full identity (or even gateway). + /// + /// Ends up with `NymMessage::Reply` variant Reply { - reply_surb: ReplySurb, + recipient_tag: AnonymousSenderTag, data: Vec, + lane: TransmissionLane, }, } impl InputMessage { - pub fn new_fresh( + pub fn new_regular(recipient: Recipient, data: Vec, lane: TransmissionLane) -> Self { + InputMessage::Regular { + recipient, + data, + lane, + } + } + + pub fn new_anonymous( recipient: Recipient, data: Vec, - with_reply_surb: bool, + reply_surbs: u32, lane: TransmissionLane, ) -> Self { - InputMessage::Fresh { + InputMessage::Anonymous { recipient, data, - with_reply_surb, + reply_surbs, lane, } } - pub fn new_reply(reply_surb: ReplySurb, data: Vec) -> Self { - InputMessage::Reply { reply_surb, data } + pub fn new_reply( + recipient_tag: AnonymousSenderTag, + data: Vec, + lane: TransmissionLane, + ) -> Self { + InputMessage::Reply { + recipient_tag, + data, + lane, + } } } diff --git a/clients/client-core/src/client/mix_traffic.rs b/clients/client-core/src/client/mix_traffic.rs index d5702cc0a8..d209d0ca4a 100644 --- a/clients/client-core/src/client/mix_traffic.rs +++ b/clients/client-core/src/client/mix_traffic.rs @@ -41,6 +41,16 @@ impl MixTrafficController { async fn on_messages(&mut self, mut mix_packets: Vec) { debug_assert!(!mix_packets.is_empty()); + // // simulate some dropped packets + // use rand::rngs::OsRng; + // use rand::Rng; + // let mut rng = OsRng; + // let number = rng.gen_range(0, 100); + // if number > 95 { + // error!("simulating dropped packet"); + // return; + // } + let result = if mix_packets.len() == 1 { let mix_packet = mix_packets.pop().unwrap(); self.gateway_client.send_mix_packet(mix_packet).await diff --git a/clients/client-core/src/client/mod.rs b/clients/client-core/src/client/mod.rs index bce1a089dc..c1e382fab0 100644 --- a/clients/client-core/src/client/mod.rs +++ b/clients/client-core/src/client/mod.rs @@ -6,8 +6,7 @@ pub mod key_manager; pub mod mix_traffic; pub mod real_messages_control; pub mod received_buffer; -#[cfg(feature = "reply-surb")] -pub mod reply_key_storage; +pub mod replies; pub mod topology_control; // This is *NOT* used to signal shutdown. diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs index 9b285d8444..c05a3c4ed8 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::action_controller::{Action, ActionSender}; +use super::action_controller::{AckActionSender, Action}; use futures::StreamExt; use gateway_client::AcknowledgementReceiver; use log::*; @@ -16,14 +16,14 @@ use std::sync::Arc; pub(super) struct AcknowledgementListener { ack_key: Arc, ack_receiver: AcknowledgementReceiver, - action_sender: ActionSender, + action_sender: AckActionSender, } impl AcknowledgementListener { pub(super) fn new( ack_key: Arc, ack_receiver: AcknowledgementReceiver, - action_sender: ActionSender, + action_sender: AckActionSender, ) -> Self { AcknowledgementListener { ack_key, @@ -50,6 +50,7 @@ impl AcknowledgementListener { trace!("Received an ack for a cover message - no need to do anything"); return; } else if frag_id.is_reply() { + error!("please let @jstuczyn know if you see this message"); info!("Received an ack for a reply message - no need to do anything! (don't know what to do!)"); // TODO: probably there will need to be some extra procedure here, something to notify // user that his reply reached the recipient (since we got an ack) diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index 2b21639230..16f5020f5d 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -3,7 +3,7 @@ use super::PendingAcknowledgement; use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender; -use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use futures::channel::mpsc; use futures::StreamExt; use log::*; use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey}; @@ -13,7 +13,8 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; -pub(crate) type ActionSender = UnboundedSender; +pub(crate) type AckActionSender = mpsc::UnboundedSender; +pub(crate) type AckActionReceiver = mpsc::UnboundedReceiver; // The actual data being sent off as well as potential key to the delay queue type PendingAckEntry = (Arc, Option); @@ -95,7 +96,7 @@ pub(super) struct ActionController { pending_acks_timers: NonExhaustiveDelayQueue, /// Channel for receiving `Action`s from other modules. - incoming_actions: UnboundedReceiver, + incoming_actions: AckActionReceiver, /// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements. retransmission_sender: RetransmissionRequestSender, @@ -105,18 +106,15 @@ impl ActionController { pub(super) fn new( config: Config, retransmission_sender: RetransmissionRequestSender, - ) -> (Self, ActionSender) { - let (sender, receiver) = mpsc::unbounded(); - ( - ActionController { - config, - pending_acks_data: HashMap::new(), - pending_acks_timers: NonExhaustiveDelayQueue::new(), - incoming_actions: receiver, - retransmission_sender, - }, - sender, - ) + incoming_actions: AckActionReceiver, + ) -> Self { + ActionController { + config, + pending_acks_data: HashMap::new(), + pending_acks_timers: NonExhaustiveDelayQueue::new(), + incoming_actions, + retransmission_sender, + } } fn handle_insert(&mut self, pending_acks: Vec) { @@ -143,8 +141,7 @@ impl ActionController { // timer TWICE for the SAME PendingAcknowledgement panic!("Tried to start an already started ack timer!") } - let timeout = (pending_ack_data.delay.clone() * self.config.ack_wait_multiplier) - .to_duration() + let timeout = (pending_ack_data.delay * self.config.ack_wait_multiplier).to_duration() + self.config.ack_wait_addition; let new_queue_key = self.pending_acks_timers.insert(frag_id, timeout); diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 0738d0aa80..0f667024e6 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -1,23 +1,14 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::action_controller::{Action, ActionSender}; -use super::PendingAcknowledgement; -use crate::client::{ - inbound_messages::{InputMessage, InputMessageReceiver}, - real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, - topology_control::TopologyAccessor, -}; +use crate::client::inbound_messages::{InputMessage, InputMessageReceiver}; +use crate::client::real_messages_control::message_handler::MessageHandler; +use crate::client::replies::reply_controller::ReplyControllerSender; use client_connections::TransmissionLane; use log::*; -use nymsphinx::anonymous_replies::ReplySurb; -use nymsphinx::preparer::MessagePreparer; -use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient}; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use rand::{CryptoRng, Rng}; -use std::sync::Arc; - -#[cfg(feature = "reply-surb")] -use crate::client::reply_key_storage::ReplyKeyStorage; /// Module responsible for dealing with the received messages: splitting them, creating acknowledgements, /// putting everything into sphinx packets, etc. @@ -26,15 +17,9 @@ pub(super) struct InputMessageListener where R: CryptoRng + Rng, { - ack_key: Arc, - ack_recipient: Recipient, input_receiver: InputMessageReceiver, - message_preparer: MessagePreparer, - action_sender: ActionSender, - real_message_sender: BatchRealMessageSender, - topology_access: TopologyAccessor, - #[cfg(feature = "reply-surb")] - reply_key_storage: ReplyKeyStorage, + message_handler: MessageHandler, + reply_controller_sender: ReplyControllerSender, } impl InputMessageListener @@ -45,153 +30,83 @@ where // some considerable refactoring #[allow(clippy::too_many_arguments)] pub(super) fn new( - ack_key: Arc, - ack_recipient: Recipient, input_receiver: InputMessageReceiver, - message_preparer: MessagePreparer, - action_sender: ActionSender, - real_message_sender: BatchRealMessageSender, - topology_access: TopologyAccessor, - #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, + message_handler: MessageHandler, + reply_controller_sender: ReplyControllerSender, ) -> Self { InputMessageListener { - ack_key, - ack_recipient, input_receiver, - message_preparer, - action_sender, - real_message_sender, - topology_access, - #[cfg(feature = "reply-surb")] - reply_key_storage, + message_handler, + reply_controller_sender, } } - // we require topology for replies to generate surb_acks - async fn handle_reply(&mut self, reply_surb: ReplySurb, data: Vec) -> Option { - let topology_permit = self.topology_access.get_read_permit().await; - let topology = match topology_permit.try_get_valid_topology_ref(&self.ack_recipient, None) { - Some(topology_ref) => topology_ref, - None => { - warn!("Could not process the message - the network topology is invalid"); - return None; - } - }; + async fn handle_reply( + &mut self, + recipient_tag: AnonymousSenderTag, + data: Vec, + lane: TransmissionLane, + ) { + // offload reply handling to the dedicated task + self.reply_controller_sender + .send_reply(recipient_tag, data, lane) + } - match self - .message_preparer - .prepare_reply_for_use(data, reply_surb, topology, &self.ack_key) + async fn handle_plain_message( + &mut self, + recipient: Recipient, + content: Vec, + lane: TransmissionLane, + ) { + if let Err(err) = self + .message_handler + .try_send_plain_message(recipient, content, lane) .await { - Ok((mix_packet, reply_id)) => { - // TODO: later probably write pending ack here - // and deal with them.... - // ... somehow - Some(RealMessage::new(mix_packet, reply_id)) - } - Err(err) => { - // TODO: should we have some mechanism to indicate to the user that the `reply_surb` - // could be reused since technically it wasn't used up here? - warn!("failed to deal with received reply surb - {:?}", err); - None - } + warn!("failed to send a plain message - {err}") } } - async fn handle_fresh_message( + async fn handle_repliable_message( &mut self, recipient: Recipient, content: Vec, - with_reply_surb: bool, - ) -> Option> { - log::trace!("handling msg size: {}", content.len()); - let topology_permit = self.topology_access.get_read_permit().await; - let topology = match topology_permit - .try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient)) + reply_surbs: u32, + lane: TransmissionLane, + ) { + if let Err(err) = self + .message_handler + .try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane) + .await { - Some(topology_ref) => topology_ref, - None => { - warn!("Could not process the message - the network topology is invalid"); - return None; - } - }; - - // split the message, attach optional reply surb - let (split_message, reply_key) = self - .message_preparer - .prepare_and_split_message(content, with_reply_surb, topology) - .expect("somehow the topology was invalid after all!"); - - #[cfg(feature = "reply-surb")] - if let Some(reply_key) = reply_key { - self.reply_key_storage - .insert_encryption_key(reply_key) - .expect("Failed to insert surb reply key to the store!") + warn!("failed to send a repliable message - {err}") } - - #[cfg(not(feature = "reply-surb"))] - let _reply_key = reply_key; - - // encrypt chunks, put them inside sphinx packets and generate acks - let mut pending_acks = Vec::with_capacity(split_message.len()); - let mut real_messages = Vec::with_capacity(split_message.len()); - for message_chunk in split_message { - // we need to clone it because we need to keep it in memory in case we had to retransmit - // it. And then we'd need to recreate entire ACK again. - let chunk_clone = message_chunk.clone(); - let prepared_fragment = self - .message_preparer - .prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient) - .unwrap(); - - real_messages.push(RealMessage::new( - prepared_fragment.mix_packet, - message_chunk.fragment_identifier(), - )); - - pending_acks.push(PendingAcknowledgement::new( - message_chunk, - prepared_fragment.total_delay, - recipient, - )); - } - - // tells the controller to put this into the hashmap - self.action_sender - .unbounded_send(Action::new_insert(pending_acks)) - .unwrap(); - - Some(real_messages) } async fn on_input_message(&mut self, msg: InputMessage) { - let (real_messages, lane) = match msg { - InputMessage::Fresh { + match msg { + InputMessage::Regular { recipient, data, - with_reply_surb, lane, - } => ( - self.handle_fresh_message(recipient, data, with_reply_surb) - .await, + } => self.handle_plain_message(recipient, data, lane).await, + InputMessage::Anonymous { + recipient, + data, + reply_surbs, lane, - ), - InputMessage::Reply { reply_surb, data } => ( - self.handle_reply(reply_surb, data) + } => { + self.handle_repliable_message(recipient, data, reply_surbs, lane) .await - .map(|message| vec![message]), - TransmissionLane::Reply, - ), + } + InputMessage::Reply { + recipient_tag, + data, + lane, + } => { + self.handle_reply(recipient_tag, data, lane).await; + } }; - - // there's no point in trying to send nothing - if let Some(real_messages) = real_messages { - // tells real message sender (with the poisson timer) to send this to the mix network - self.real_message_sender - .send((real_messages, lane)) - .await - .expect("BatchRealMessageReceiver has stopped receiving!"); - } } #[cfg(not(target_arch = "wasm32"))] diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index c946716091..eb5f9aa494 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -7,18 +7,20 @@ use self::{ retransmission_request_listener::RetransmissionRequestListener, sent_notification_listener::SentNotificationListener, }; -use super::real_traffic_stream::BatchRealMessageSender; -use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor}; +use crate::client::inbound_messages::InputMessageReceiver; +use crate::client::real_messages_control::message_handler::MessageHandler; +use crate::client::replies::reply_controller::ReplyControllerSender; use crate::spawn_future; +use action_controller::AckActionReceiver; use futures::channel::mpsc; use gateway_client::AcknowledgementReceiver; use log::*; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::params::PacketSize; use nymsphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, chunking::fragment::{Fragment, FragmentIdentifier}, - preparer::MessagePreparer, Delay as SphinxDelay, }; use rand::{CryptoRng, Rng}; @@ -27,8 +29,8 @@ use std::{ time::Duration, }; -#[cfg(feature = "reply-surb")] -use crate::client::reply_key_storage::ReplyKeyStorage; +use crate::client::replies::reply_storage::ReceivedReplySurbsMap; +pub(crate) use action_controller::{AckActionSender, Action}; mod acknowledgement_listener; mod action_controller; @@ -50,21 +52,53 @@ pub(super) type SentPacketNotificationSender = mpsc::UnboundedSender; +#[derive(Debug)] +pub(crate) enum PacketDestination { + Anonymous { + recipient_tag: AnonymousSenderTag, + // special flag to indicate whether this was an ack for requesting additional surbs, + // in that case we have to do everything we can to get it through, even if it means going + // below our stored reply surb threshold + extra_surb_request: bool, + }, + KnownRecipient(Box), +} + /// Structure representing a data `Fragment` that is on-route to the specified `Recipient` #[derive(Debug)] pub(crate) struct PendingAcknowledgement { message_chunk: Fragment, delay: SphinxDelay, - recipient: Recipient, + destination: PacketDestination, } impl PendingAcknowledgement { /// Creates new instance of `PendingAcknowledgement` using the provided data. - fn new(message_chunk: Fragment, delay: SphinxDelay, recipient: Recipient) -> Self { + pub(crate) fn new_known( + message_chunk: Fragment, + delay: SphinxDelay, + recipient: Recipient, + ) -> Self { PendingAcknowledgement { message_chunk, delay, - recipient, + destination: PacketDestination::KnownRecipient(recipient.into()), + } + } + + pub(crate) fn new_anonymous( + message_chunk: Fragment, + delay: SphinxDelay, + recipient_tag: AnonymousSenderTag, + extra_surb_request: bool, + ) -> Self { + PendingAcknowledgement { + message_chunk, + delay, + destination: PacketDestination::Anonymous { + recipient_tag, + extra_surb_request, + }, } } @@ -76,10 +110,6 @@ impl PendingAcknowledgement { /// AcknowledgementControllerConnectors represents set of channels for communication with /// other parts of the system in order to support acknowledgements and retransmission. 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: BatchRealMessageSender, - /// Channel used for receiving raw messages from a client. The messages need to be put /// into sphinx packets first. input_receiver: InputMessageReceiver, @@ -91,20 +121,28 @@ pub(super) struct AcknowledgementControllerConnectors { /// Channel used for receiving acknowledgements from the mix network. ack_receiver: AcknowledgementReceiver, + + /// Channel used for sending request to `ActionController` to deal with anything ack-related, + ack_action_sender: AckActionSender, + + /// Channel used for receiving request by `ActionController` to deal with anything ack-related, + ack_action_receiver: AckActionReceiver, } impl AcknowledgementControllerConnectors { pub(super) fn new( - real_message_sender: BatchRealMessageSender, input_receiver: InputMessageReceiver, sent_notifier: SentPacketNotificationReceiver, ack_receiver: AcknowledgementReceiver, + ack_action_sender: AckActionSender, + ack_action_receiver: AckActionReceiver, ) -> Self { AcknowledgementControllerConnectors { - real_message_sender, input_receiver, sent_notifier, ack_receiver, + ack_action_sender, + ack_action_receiver, } } } @@ -117,11 +155,8 @@ pub(super) struct Config { /// Given ack timeout in the form a * BASE_DELAY + b, it specifies the multiplier `a` ack_wait_multiplier: f64, - /// Average delay an acknowledgement packet is going to get delayed at a single mixnode. - average_ack_delay: Duration, - - /// Average delay a data packet is going to get delayed at a single mixnode. - average_packet_delay: Duration, + /// Defines the amount of reply surbs that the client is going to request when it runs out while attempting to retransmit packets. + retransmission_reply_surb_request_size: u32, /// Predefined packet size used for the encapsulated messages. packet_size: PacketSize, @@ -131,14 +166,12 @@ impl Config { pub(super) fn new( ack_wait_addition: Duration, ack_wait_multiplier: f64, - average_ack_delay: Duration, - average_packet_delay: Duration, + retransmission_reply_surb_request_size: u32, ) -> Self { Config { ack_wait_addition, ack_wait_multiplier, - average_ack_delay, - average_packet_delay, + retransmission_reply_surb_request_size, packet_size: Default::default(), } } @@ -162,68 +195,53 @@ where impl AcknowledgementController where - R: 'static + CryptoRng + Rng + Clone + Send, + R: 'static + CryptoRng + Rng + Clone + Send + Sync, { - #[allow(clippy::too_many_arguments)] pub(super) fn new( config: Config, - rng: R, - topology_access: TopologyAccessor, ack_key: Arc, - ack_recipient: Recipient, connectors: AcknowledgementControllerConnectors, - #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, + message_handler: MessageHandler, + reply_controller_sender: ReplyControllerSender, + received_reply_surbs: ReceivedReplySurbsMap, ) -> Self { let (retransmission_tx, retransmission_rx) = mpsc::unbounded(); let action_config = action_controller::Config::new(config.ack_wait_addition, config.ack_wait_multiplier); - let (action_controller, action_sender) = - ActionController::new(action_config, retransmission_tx); - - let message_preparer = MessagePreparer::new( - rng, - ack_recipient, - config.average_packet_delay, - config.average_ack_delay, - ) - .with_custom_real_message_packet_size(config.packet_size); + let action_controller = ActionController::new( + action_config, + retransmission_tx, + connectors.ack_action_receiver, + ); // will listen for any acks coming from the network let acknowledgement_listener = AcknowledgementListener::new( Arc::clone(&ack_key), connectors.ack_receiver, - action_sender.clone(), + connectors.ack_action_sender.clone(), ); // will listen for any new messages from the client let input_message_listener = InputMessageListener::new( - Arc::clone(&ack_key), - ack_recipient, connectors.input_receiver, - message_preparer.clone(), - action_sender.clone(), - connectors.real_message_sender.clone(), - topology_access.clone(), - #[cfg(feature = "reply-surb")] - reply_key_storage, + message_handler.clone(), + reply_controller_sender, ); // will listen for any ack timeouts and trigger retransmission let retransmission_request_listener = RetransmissionRequestListener::new( - Arc::clone(&ack_key), - ack_recipient, - message_preparer, - action_sender.clone(), - connectors.real_message_sender, + connectors.ack_action_sender.clone(), + message_handler, retransmission_rx, - topology_access, + received_reply_surbs, + config.retransmission_reply_surb_request_size, ); // will listen for events indicating the packet was sent through the network so that // the retransmission timer should be started. let sent_notification_listener = - SentNotificationListener::new(connectors.sent_notifier, action_sender); + SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender); AcknowledgementController { acknowledgement_listener, diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index 4b0bc3cc99..14f9205700 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -2,59 +2,150 @@ // SPDX-License-Identifier: Apache-2.0 use super::{ - action_controller::{Action, ActionSender}, + action_controller::{AckActionSender, Action}, PendingAcknowledgement, RetransmissionRequestReceiver, }; -use crate::client::{ - real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, - topology_control::TopologyAccessor, -}; - +use crate::client::real_messages_control::acknowledgement_control::PacketDestination; +use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError}; +use crate::client::real_messages_control::real_traffic_stream::RealMessage; +use crate::client::replies::reply_storage::ReceivedReplySurbsMap; use client_connections::TransmissionLane; use futures::StreamExt; use log::*; -use nymsphinx::{ - acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer, -}; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nymsphinx::chunking::fragment::Fragment; +use nymsphinx::preparer::PreparedFragment; use rand::{CryptoRng, Rng}; use std::sync::{Arc, Weak}; // responsible for packet retransmission upon fired timer -pub(super) struct RetransmissionRequestListener -where - R: CryptoRng + Rng, -{ - ack_key: Arc, - ack_recipient: Recipient, - message_preparer: MessagePreparer, - action_sender: ActionSender, - real_message_sender: BatchRealMessageSender, +pub(super) struct RetransmissionRequestListener { + action_sender: AckActionSender, + message_handler: MessageHandler, request_receiver: RetransmissionRequestReceiver, - topology_access: TopologyAccessor, + + // we're holding this for the purposes of retransmitting dropped reply message, but perhaps + // this work should be offloaded to the `ReplyController`? + received_reply_surbs: ReceivedReplySurbsMap, + + reply_surb_request_size: u32, } impl RetransmissionRequestListener where R: CryptoRng + Rng, { - #[allow(clippy::too_many_arguments)] pub(super) fn new( - ack_key: Arc, - ack_recipient: Recipient, - message_preparer: MessagePreparer, - action_sender: ActionSender, - real_message_sender: BatchRealMessageSender, + action_sender: AckActionSender, + message_handler: MessageHandler, request_receiver: RetransmissionRequestReceiver, - topology_access: TopologyAccessor, + received_reply_surbs: ReceivedReplySurbsMap, + reply_surb_request_size: u32, ) -> Self { RetransmissionRequestListener { - ack_key, - ack_recipient, - message_preparer, action_sender, - real_message_sender, + message_handler, request_receiver, - topology_access, + received_reply_surbs, + reply_surb_request_size, + } + } + + async fn prepare_normal_retransmission_chunk( + &mut self, + packet_recipient: Recipient, + chunk_data: Fragment, + ) -> Result { + debug!("retransmitting normal packet..."); + + self.message_handler + .try_prepare_single_chunk_for_sending(packet_recipient, chunk_data) + .await + } + + async fn prepare_reply_retransmission_chunk( + &mut self, + recipient_tag: AnonymousSenderTag, + extra_surb_request: bool, + chunk_data: Fragment, + ) -> Result { + debug!("retransmitting reply packet..."); + + let surbs_left = self.received_reply_surbs.available_surbs(&recipient_tag); + trace!("{surbs_left} surbs left"); + + // if this is retransmission for obtaining additional reply surbs, + // we can dip below the storage threshold + let (maybe_reply_surb, surbs_left) = if extra_surb_request { + self.received_reply_surbs + .get_reply_surb_ignoring_threshold(&recipient_tag) + } else { + self.received_reply_surbs.get_reply_surb(&recipient_tag) + } + .ok_or(PreparationError::UnknownSurbSender { + sender_tag: recipient_tag, + })?; + + let min_surb_threshold = self.received_reply_surbs.min_surb_threshold(); + + // but if it wasn't a retransmission for obtaining additional reply surbs + // and we're now about to go below threshold, attempt to request additional surbs + if !extra_surb_request && surbs_left <= (min_surb_threshold + 1) { + // if we're running low on surbs, we should request more (unless we've already requested them) + let pending_reception = self.received_reply_surbs.pending_reception(&recipient_tag); + + if pending_reception < self.reply_surb_request_size { + trace!("requesting surbs from retransmission handler"); + + // TODO: is this logic for surb request possibly shared with other parts already? + if let Some(another_surb) = self + .received_reply_surbs + .get_reply_surb_ignoring_threshold(&recipient_tag) + .ok_or(PreparationError::UnknownSurbSender { + sender_tag: recipient_tag, + })? + .0 + { + if let Err(err) = self + .message_handler + .try_request_additional_reply_surbs( + recipient_tag, + another_surb, + self.reply_surb_request_size, + ) + .await + { + let err = + err.return_unused_surbs(&self.received_reply_surbs, &recipient_tag); + warn!("we failed to ask for more surbs... - {err}"); + // TODO: should we return here instead? + } + self.received_reply_surbs + .increment_pending_reception(&recipient_tag, self.reply_surb_request_size) + .ok_or(PreparationError::UnknownSurbSender { + sender_tag: recipient_tag, + })?; + } + } + } + + let Some(reply_surb) = maybe_reply_surb else { + warn!("we run out of reply surbs for {:?} to retransmit our dropped message...", recipient_tag); + return Err(PreparationError::NotEnoughSurbs { available: 0, required: 1 }) + }; + + match self + .message_handler + .try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_data) + .await + { + Ok(prepared_fragment) => Ok(prepared_fragment), + Err(err) => { + let err = err.return_unused_surbs(&self.received_reply_surbs, &recipient_tag); + warn!("failed to prepare message for retransmission - {err}",); + Err(err) + } } } @@ -66,18 +157,39 @@ where return; } }; - let packet_recipient = &timed_out_ack.recipient; + let chunk_clone = timed_out_ack.message_chunk.clone(); let frag_id = chunk_clone.fragment_identifier(); - let topology_permit = self.topology_access.get_read_permit().await; - let topology_ref = match topology_permit - .try_get_valid_topology_ref(&self.ack_recipient, Some(packet_recipient)) - { - Some(topology_ref) => topology_ref, - None => { - warn!("Could not retransmit the packet - the network topology is invalid"); + let maybe_prepared_fragment = match &timed_out_ack.destination { + PacketDestination::Anonymous { + recipient_tag, + extra_surb_request, + } => { + self.prepare_reply_retransmission_chunk( + *recipient_tag, + *extra_surb_request, + chunk_clone, + ) + .await + } + PacketDestination::KnownRecipient(recipient) => { + self.prepare_normal_retransmission_chunk(**recipient, chunk_clone) + .await + } + }; + + let prepared_fragment = match maybe_prepared_fragment { + Ok(prepared_fragment) => prepared_fragment, + Err(err) => { + warn!("Could not retransmit the packet - {err}"); // we NEED to start timer here otherwise we will have this guy permanently stuck in memory + + // TODO: purge the entry from memory if it was an ack for reply packet and we're out of surbs + // self.action_sender + // .unbounded_send(Action::new_remove(frag_id)) + // .unwrap(); + self.action_sender .unbounded_send(Action::new_start_timer(frag_id)) .unwrap(); @@ -85,11 +197,6 @@ where } }; - let prepared_fragment = self - .message_preparer - .prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient) - .unwrap(); - // if we have the ONLY strong reference to the ack data, it means it was removed from the // pending acks if Arc::strong_count(&timed_out_ack) == 1 { @@ -101,7 +208,6 @@ where // we no longer need the reference - let's drop it so that if somehow `UpdateTimer` action // reached the controller before this function terminated, the controller would not panic. drop(timed_out_ack); - let new_delay = prepared_fragment.total_delay; // We know this update will be reflected by the `StartTimer` Action performed when this @@ -116,13 +222,12 @@ where .unwrap(); // send to `OutQueueControl` to eventually send to the mix network - self.real_message_sender - .send(( + self.message_handler + .forward_messages( vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)], TransmissionLane::Retransmission, - )) + ) .await - .expect("BatchRealMessageReceiver has stopped receiving!"); } #[cfg(not(target_arch = "wasm32"))] diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs index 5206a602ce..43d43bb7a6 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::action_controller::{Action, ActionSender}; +use super::action_controller::{AckActionSender, Action}; use super::SentPacketNotificationReceiver; use futures::StreamExt; use log::*; @@ -13,13 +13,13 @@ use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID}; /// accidentally fire retransmission way quicker than we should have. pub(super) struct SentNotificationListener { sent_notifier: SentPacketNotificationReceiver, - action_sender: ActionSender, + action_sender: AckActionSender, } impl SentNotificationListener { pub(super) fn new( sent_notifier: SentPacketNotificationReceiver, - action_sender: ActionSender, + action_sender: AckActionSender, ) -> Self { SentNotificationListener { sent_notifier, @@ -32,6 +32,7 @@ impl SentNotificationListener { trace!("sent off a cover message - no need to start retransmission timer!"); return; } else if frag_id.is_reply() { + error!("please let @jstuczyn know if you see this message"); debug!("sent off a reply message - no need to start retransmission timer!"); // TODO: probably there will need to be some extra procedure here, like it would // be nice to know that our reply actually reached the recipient (i.e. we got the ack) diff --git a/clients/client-core/src/client/real_messages_control/message_handler.rs b/clients/client-core/src/client/real_messages_control/message_handler.rs new file mode 100644 index 0000000000..54b644f7ed --- /dev/null +++ b/clients/client-core/src/client/real_messages_control/message_handler.rs @@ -0,0 +1,447 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement; +use crate::client::real_messages_control::real_traffic_stream::{ + BatchRealMessageSender, RealMessage, +}; +use crate::client::real_messages_control::{AckActionSender, Action}; +use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags}; +use crate::client::topology_control::{InvalidTopologyError, TopologyAccessor, TopologyReadPermit}; +use client_connections::TransmissionLane; +use log::{debug, error, info, trace, warn}; +use nymsphinx::acknowledgements::AckKey; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::anonymous_replies::requests::{ + AnonymousSenderTag, RepliableMessage, ReplyMessage, SENDER_TAG_SIZE, +}; +use nymsphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey}; +use nymsphinx::chunking::fragment::Fragment; +use nymsphinx::message::NymMessage; +use nymsphinx::preparer::{MessagePreparer, PreparedFragment}; +use rand::{CryptoRng, Rng}; +use std::sync::Arc; +use thiserror::Error; +use topology::{NymTopology, NymTopologyError}; + +// TODO: move that error elsewhere since it seems to be contaminating different files +// TODO2: attempt to unify `InvalidTopologyError` and `NymTopologyError` +#[derive(Debug, Clone, Error)] +pub enum PreparationError { + #[error(transparent)] + InvalidTopology(#[from] InvalidTopologyError), + + #[error(transparent)] + NymTopologyError(#[from] NymTopologyError), + + #[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")] + MessageTooLongForSingleSurb { fragments: usize }, + + #[error( + "Never received any reply SURBs associated with the following sender tag: {sender_tag:?}" + )] + UnknownSurbSender { sender_tag: AnonymousSenderTag }, + + #[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")] + NotEnoughSurbs { available: usize, required: usize }, +} + +impl PreparationError { + fn return_surbs(self, returned_surbs: Vec) -> SurbWrappedPreparationError { + SurbWrappedPreparationError { + source: self, + returned_surbs: Some(returned_surbs), + } + } +} + +#[derive(Debug, Error)] +#[error("Failed to prepare packets - {source}. {} reply surbs will be returned", .returned_surbs.as_ref().map(|s| s.len()).unwrap_or_default())] +pub struct SurbWrappedPreparationError { + #[source] + source: PreparationError, + + returned_surbs: Option>, +} + +impl From for SurbWrappedPreparationError +where + T: Into, +{ + fn from(err: T) -> Self { + SurbWrappedPreparationError { + source: err.into(), + returned_surbs: None, + } + } +} + +impl SurbWrappedPreparationError { + pub(crate) fn return_unused_surbs( + self, + surb_storage: &ReceivedReplySurbsMap, + target: &AnonymousSenderTag, + ) -> PreparationError { + if let Some(reply_surbs) = self.returned_surbs { + surb_storage.insert_surbs(target, reply_surbs) + } + self.source + } +} + +#[derive(Clone)] +pub(crate) struct MessageHandler { + rng: R, + ack_key: Arc, + self_address: Recipient, + message_preparer: MessagePreparer, + action_sender: AckActionSender, + real_message_sender: BatchRealMessageSender, + topology_access: TopologyAccessor, + reply_key_storage: SentReplyKeys, + tag_storage: UsedSenderTags, +} + +impl MessageHandler +where + R: CryptoRng + Rng, +{ + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + rng: R, + ack_key: Arc, + self_address: Recipient, + message_preparer: MessagePreparer, + action_sender: AckActionSender, + real_message_sender: BatchRealMessageSender, + topology_access: TopologyAccessor, + reply_key_storage: SentReplyKeys, + tag_storage: UsedSenderTags, + ) -> Self { + MessageHandler { + rng, + ack_key, + self_address, + message_preparer, + action_sender, + real_message_sender, + topology_access, + reply_key_storage, + tag_storage, + } + } + + fn get_or_create_sender_tag(&mut self, recipient: &Recipient) -> AnonymousSenderTag { + if let Some(existing) = self.tag_storage.try_get_existing(recipient) { + trace!("we already had sender tag for {recipient}"); + existing + } else { + info!("creating new sender tag for {recipient}"); + let mut new_tag = [0u8; SENDER_TAG_SIZE]; + self.rng.fill_bytes(&mut new_tag); + self.tag_storage.insert_new(recipient, new_tag); + new_tag + } + } + + fn get_topology<'a>( + &self, + permit: &'a TopologyReadPermit<'a>, + ) -> Result<&'a NymTopology, PreparationError> { + match permit.try_get_valid_topology_ref(&self.self_address, None) { + Ok(topology_ref) => Ok(topology_ref), + Err(err) => { + warn!("Could not process the packet - the network topology is invalid - {err}"); + Err(err.into()) + } + } + } + + async fn generate_reply_surbs_with_keys( + &mut self, + amount: usize, + ) -> Result<(Vec, Vec), PreparationError> { + let topology_permit = self.topology_access.get_read_permit().await; + let topology = self.get_topology(&topology_permit)?; + + let reply_surbs = self + .message_preparer + .generate_reply_surbs(amount, topology)?; + + let reply_keys = reply_surbs + .iter() + .map(|s| *s.encryption_key()) + .collect::>(); + + Ok((reply_surbs, reply_keys)) + } + + pub(crate) async fn try_send_single_surb_message( + &mut self, + target: AnonymousSenderTag, + message: ReplyMessage, + reply_surb: ReplySurb, + is_extra_surb_request: bool, + ) -> Result<(), SurbWrappedPreparationError> { + let mut fragment = self + .message_preparer + .pad_and_split_message(NymMessage::new_reply(message)); + if fragment.len() > 1 { + // well, it's not a single surb message + return Err(SurbWrappedPreparationError { + source: PreparationError::MessageTooLongForSingleSurb { + fragments: fragment.len(), + }, + returned_surbs: Some(vec![reply_surb]), + }); + } + + let chunk = fragment.pop().unwrap(); + let chunk_clone = chunk.clone(); + let prepared_fragment = self + .try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone) + .await?; + + let real_messages = + RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier()); + let delay = prepared_fragment.total_delay; + let pending_ack = + PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request); + + let lane = if is_extra_surb_request { + TransmissionLane::ReplySurbRequest + } else { + TransmissionLane::General + }; + + self.forward_messages(vec![real_messages], lane).await; + self.insert_pending_acks(vec![pending_ack]); + Ok(()) + } + + pub(crate) async fn try_request_additional_reply_surbs( + &mut self, + from: AnonymousSenderTag, + reply_surb: ReplySurb, + amount: u32, + ) -> Result<(), SurbWrappedPreparationError> { + debug!("requesting {amount} reply SURBs from {from:?}"); + + let surbs_request = ReplyMessage::new_surb_request_message(self.self_address, amount); + self.try_send_single_surb_message(from, surbs_request, reply_surb, true) + .await + } + + // // TODO: this will require additional argument to make it use different variant of `ReplyMessage` + pub(crate) fn split_reply_message(&mut self, message: Vec) -> Vec { + self.message_preparer + .pad_and_split_message(NymMessage::new_reply(ReplyMessage::new_data_message( + message, + ))) + } + + pub(crate) async fn try_send_reply_chunks( + &mut self, + target: AnonymousSenderTag, + fragments: Vec, + reply_surbs: Vec, + lane: TransmissionLane, + ) -> Result<(), SurbWrappedPreparationError> { + // this should never be reached! + debug_assert_ne!( + fragments.len(), + reply_surbs.len(), + "attempted to send {} fragments with {} reply surbs", + fragments.len(), + reply_surbs.len() + ); + + let topology_permit = self.topology_access.get_read_permit().await; + let topology = match self.get_topology(&topology_permit) { + Ok(topology) => topology, + Err(err) => return Err(err.return_surbs(reply_surbs)), + }; + + let mut pending_acks = Vec::with_capacity(fragments.len()); + let mut real_messages = Vec::with_capacity(fragments.len()); + for (fragment, reply_surb) in fragments.into_iter().zip(reply_surbs.into_iter()) { + // we need to clone it because we need to keep it in memory in case we had to retransmit + // it. And then we'd need to recreate entire ACK again. + let chunk_clone = fragment.clone(); + let prepared_fragment = self + .message_preparer + .prepare_reply_chunk_for_sending(chunk_clone, topology, &self.ack_key, reply_surb) + .unwrap(); + + let real_message = + RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier()); + let delay = prepared_fragment.total_delay; + let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false); + + real_messages.push(real_message); + pending_acks.push(pending_ack); + } + + self.forward_messages(real_messages, lane).await; + self.insert_pending_acks(pending_acks); + Ok(()) + } + + pub(crate) async fn try_send_plain_message( + &mut self, + recipient: Recipient, + message: Vec, + lane: TransmissionLane, + ) -> Result<(), PreparationError> { + let message = NymMessage::new_plain(message); + self.try_split_and_send_non_reply_message(message, recipient, lane) + .await + } + + pub(crate) async fn try_split_and_send_non_reply_message( + &mut self, + message: NymMessage, + recipient: Recipient, + lane: TransmissionLane, + ) -> Result<(), PreparationError> { + // TODO: I really dislike existence of this assertion, it implies code has to be re-organised + debug_assert!(!matches!(message, NymMessage::Reply(_))); + + // TODO2: it's really annoying we have to get topology permit again here due to borrow-checker + let topology_permit = self.topology_access.get_read_permit().await; + let topology = self.get_topology(&topology_permit)?; + + let fragments = self.message_preparer.pad_and_split_message(message); + + let mut pending_acks = Vec::with_capacity(fragments.len()); + let mut real_messages = Vec::with_capacity(fragments.len()); + for fragment in fragments { + // we need to clone it because we need to keep it in memory in case we had to retransmit + // it. And then we'd need to recreate entire ACK again. + let chunk_clone = fragment.clone(); + let prepared_fragment = self.message_preparer.prepare_chunk_for_sending( + chunk_clone, + topology, + &self.ack_key, + &recipient, + )?; + + let real_message = + RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier()); + let delay = prepared_fragment.total_delay; + let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient); + + real_messages.push(real_message); + pending_acks.push(pending_ack); + } + + self.insert_pending_acks(pending_acks); + self.forward_messages(real_messages, lane).await; + + Ok(()) + } + + pub(crate) async fn try_send_additional_reply_surbs( + &mut self, + recipient: Recipient, + amount: u32, + ) -> Result<(), PreparationError> { + let sender_tag = self.get_or_create_sender_tag(&recipient); + let (reply_surbs, reply_keys) = + self.generate_reply_surbs_with_keys(amount as usize).await?; + + let message = NymMessage::new_repliable(RepliableMessage::new_additional_surbs( + sender_tag, + reply_surbs, + )); + + self.try_split_and_send_non_reply_message( + message, + recipient, + TransmissionLane::AdditionalReplySurbs, + ) + .await?; + + log::trace!("storing {} reply keys", reply_keys.len()); + self.reply_key_storage.insert_multiple(reply_keys); + + Ok(()) + } + + pub(crate) async fn try_send_message_with_reply_surbs( + &mut self, + recipient: Recipient, + message: Vec, + num_reply_surbs: u32, + lane: TransmissionLane, + ) -> Result<(), SurbWrappedPreparationError> { + let sender_tag = self.get_or_create_sender_tag(&recipient); + let (reply_surbs, reply_keys) = self + .generate_reply_surbs_with_keys(num_reply_surbs as usize) + .await?; + + let message = + NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs)); + + self.try_split_and_send_non_reply_message(message, recipient, lane) + .await?; + + log::trace!("storing {} reply keys", reply_keys.len()); + self.reply_key_storage.insert_multiple(reply_keys); + + Ok(()) + } + + pub(crate) async fn try_prepare_single_chunk_for_sending( + &mut self, + recipient: Recipient, + chunk: Fragment, + ) -> Result { + let topology_permit = self.topology_access.get_read_permit().await; + let topology = self.get_topology(&topology_permit)?; + + let prepared_fragment = self + .message_preparer + .prepare_chunk_for_sending(chunk, topology, &self.ack_key, &recipient) + .unwrap(); + + Ok(prepared_fragment) + } + + pub(crate) async fn try_prepare_single_reply_chunk_for_sending( + &mut self, + reply_surb: ReplySurb, + chunk: Fragment, + ) -> Result { + let topology_permit = self.topology_access.get_read_permit().await; + let topology = match self.get_topology(&topology_permit) { + Ok(topology) => topology, + Err(err) => return Err(err.return_surbs(vec![reply_surb])), + }; + + let prepared_fragment = self + .message_preparer + .prepare_reply_chunk_for_sending(chunk, topology, &self.ack_key, reply_surb) + .unwrap(); + + Ok(prepared_fragment) + } + + pub(crate) fn insert_pending_acks(&self, pending_acks: Vec) { + self.action_sender + .unbounded_send(Action::new_insert(pending_acks)) + .expect("action control task has died") + } + + // tells real message sender (with the poisson timer) to send this to the mix network + pub(super) async fn forward_messages( + &self, + messages: Vec, + transmission_lane: TransmissionLane, + ) { + self.real_message_sender + .send((messages, transmission_lane)) + .await + .expect("real message receiver task (OutQueueControl) has died"); + } +} diff --git a/clients/client-core/src/client/real_messages_control/mod.rs b/clients/client-core/src/client/real_messages_control/mod.rs index ffeeea61e3..76fc5f9e43 100644 --- a/clients/client-core/src/client/real_messages_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/mod.rs @@ -8,6 +8,11 @@ use self::{ acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl, }; +use crate::client::real_messages_control::message_handler::MessageHandler; +use crate::client::replies::reply_controller::{ + ReplyController, ReplyControllerReceiver, ReplyControllerSender, +}; +use crate::client::replies::reply_storage::CombinedReplyStorage; use crate::{ client::{ inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender, @@ -23,15 +28,19 @@ use log::*; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; use nymsphinx::params::PacketSize; +use nymsphinx::preparer::MessagePreparer; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::sync::Arc; use std::time::Duration; -#[cfg(feature = "reply-surb")] -use crate::client::reply_key_storage::ReplyKeyStorage; +use crate::config; +pub(crate) use acknowledgement_control::{AckActionSender, Action}; +// #[cfg(feature = "reply-surb")] +// use crate::client::reply_key_storage::ReplyKeyStorage; -mod acknowledgement_control; -mod real_traffic_stream; +pub(crate) mod acknowledgement_control; +pub(crate) mod message_handler; +pub(crate) mod real_traffic_stream; // TODO: ack_key and self_recipient shouldn't really be part of this config pub struct Config { @@ -62,31 +71,51 @@ pub struct Config { /// Predefined packet size used for the encapsulated messages. packet_size: PacketSize, + + /// Defines the minimum number of reply surbs the client would request. + minimum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs the client would request. + maximum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. + maximum_allowed_reply_surb_request_size: u32, + + /// Defines the amount of reply surbs that the client is going to request when it runs out while attempting to retransmit packets. + retransmission_reply_surb_request_size: u32, + + /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking + /// for more even though in theory they wouldn't need to. + maximum_reply_surb_waiting_period: Duration, } impl Config { - // TODO: change the config into a builder - #[allow(clippy::too_many_arguments)] pub fn new( + base_client_debug_config: &config::Debug, ack_key: Arc, - ack_wait_multiplier: f64, - ack_wait_addition: Duration, - average_ack_delay_duration: Duration, - average_message_sending_delay: Duration, - average_packet_delay_duration: Duration, - disable_main_poisson_packet_distribution: bool, self_recipient: Recipient, ) -> Self { Config { ack_key, - ack_wait_addition, - ack_wait_multiplier, self_recipient, - average_message_sending_delay, - average_packet_delay_duration, - average_ack_delay_duration, - disable_main_poisson_packet_distribution, packet_size: Default::default(), + ack_wait_addition: base_client_debug_config.ack_wait_addition, + ack_wait_multiplier: base_client_debug_config.ack_wait_multiplier, + average_message_sending_delay: base_client_debug_config.message_sending_average_delay, + average_packet_delay_duration: base_client_debug_config.average_packet_delay, + average_ack_delay_duration: base_client_debug_config.average_ack_delay, + disable_main_poisson_packet_distribution: base_client_debug_config + .disable_main_poisson_packet_distribution, + minimum_reply_surb_request_size: base_client_debug_config + .minimum_reply_surb_request_size, + maximum_reply_surb_request_size: base_client_debug_config + .maximum_reply_surb_request_size, + maximum_allowed_reply_surb_request_size: base_client_debug_config + .maximum_allowed_reply_surb_request_size, + retransmission_reply_surb_request_size: base_client_debug_config + .retransmission_reply_surb_request_size, + maximum_reply_surb_waiting_period: base_client_debug_config + .maximum_reply_surb_waiting_period, } } @@ -101,6 +130,7 @@ where { out_queue_control: OutQueueControl, ack_control: AcknowledgementController, + reply_control: ReplyController, } // obviously when we finally make shared rng that is on 'higher' level, this should become @@ -113,7 +143,10 @@ impl RealMessagesController { input_receiver: InputMessageReceiver, mix_sender: BatchMixMessageSender, topology_access: TopologyAccessor, - #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, + reply_storage: CombinedReplyStorage, + // so much refactoring needed, but this is temporary just to test things out + reply_controller_sender: ReplyControllerSender, + reply_controller_receiver: ReplyControllerReceiver, lane_queue_lengths: LaneQueueLengths, client_connection_rx: ConnectionCommandReceiver, ) -> Self { @@ -121,31 +154,61 @@ impl RealMessagesController { let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1); let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded(); + let (ack_action_tx, ack_action_rx) = mpsc::unbounded(); let ack_controller_connectors = AcknowledgementControllerConnectors::new( - real_message_sender, input_receiver, sent_notifier_rx, ack_receiver, + ack_action_tx.clone(), + ack_action_rx, ); let ack_control_config = acknowledgement_control::Config::new( config.ack_wait_addition, config.ack_wait_multiplier, - config.average_ack_delay_duration, - config.average_packet_delay_duration, + config.retransmission_reply_surb_request_size, ) .with_custom_packet_size(config.packet_size); - let ack_control = AcknowledgementController::new( - ack_control_config, + // TODO: construct MessagePreparer itself inside the MessageHandler + let message_preparer = MessagePreparer::new( + rng, + config.self_recipient, + config.average_packet_delay_duration, + config.average_ack_delay_duration, + ) + .with_custom_real_message_packet_size(config.packet_size); + let message_handler = MessageHandler::new( rng, - topology_access.clone(), Arc::clone(&config.ack_key), config.self_recipient, + message_preparer, + ack_action_tx, + real_message_sender, + topology_access.clone(), + reply_storage.key_storage(), + reply_storage.tags_storage(), + ); + + let reply_control = ReplyController::new( + message_handler.clone(), + reply_storage.surbs_storage(), + reply_storage.tags_storage(), + reply_controller_receiver, + config.minimum_reply_surb_request_size, + config.maximum_reply_surb_request_size, + config.maximum_allowed_reply_surb_request_size, + config.maximum_reply_surb_waiting_period, + ); + + let ack_control = AcknowledgementController::new( + ack_control_config, + Arc::clone(&config.ack_key), ack_controller_connectors, - #[cfg(feature = "reply-surb")] - reply_key_storage, + message_handler, + reply_controller_sender, + reply_storage.surbs_storage(), ); let out_queue_config = real_traffic_stream::Config::new( @@ -158,7 +221,7 @@ impl RealMessagesController { let out_queue_control = OutQueueControl::new( out_queue_config, - Arc::clone(&config.ack_key), + config.ack_key, sent_notifier_tx, mix_sender, real_message_receiver, @@ -172,6 +235,7 @@ impl RealMessagesController { RealMessagesController { out_queue_control, ack_control, + reply_control, } } @@ -179,12 +243,19 @@ impl RealMessagesController { pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) { let mut out_queue_control = self.out_queue_control; let ack_control = self.ack_control; + let mut reply_control = self.reply_control; let shutdown_handle = shutdown.clone(); spawn_future(async move { out_queue_control.run_with_shutdown(shutdown_handle).await; debug!("The out queue controller has finished execution!"); }); + let shutdown_handle = shutdown.clone(); + spawn_future(async move { + reply_control.run_with_shutdown(shutdown_handle).await; + debug!("The reply controller has finished execution!"); + }); + ack_control.start_with_shutdown(shutdown); } @@ -192,11 +263,16 @@ impl RealMessagesController { pub fn start(self) { let mut out_queue_control = self.out_queue_control; let ack_control = self.ack_control; + let mut reply_control = self.reply_control; spawn_future(async move { out_queue_control.run().await; debug!("The out queue controller has finished execution!"); }); + spawn_future(async move { + reply_control.run().await; + debug!("The reply controller has finished execution!"); + }); ack_control.start(); } } diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 503203922c..a69483bec2 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -16,6 +16,7 @@ use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::cover::generate_loop_cover_packet; use nymsphinx::forwarding::packet::MixPacket; use nymsphinx::params::PacketSize; +use nymsphinx::preparer::PreparedFragment; use nymsphinx::utils::sample_poisson_duration; use rand::{CryptoRng, Rng}; use std::pin::Pin; @@ -144,6 +145,16 @@ where pub(crate) struct RealMessage { mix_packet: MixPacket, fragment_id: FragmentIdentifier, + // TODO: add info about it being constructed with reply-surb +} + +impl From<(PreparedFragment, FragmentIdentifier)> for RealMessage { + fn from((fragment, fragment_id): (PreparedFragment, FragmentIdentifier)) -> Self { + RealMessage { + mix_packet: fragment.mix_packet, + fragment_id, + } + } } impl RealMessage { @@ -220,17 +231,16 @@ where // poisson delay, but is it really a problem? let topology_permit = self.topology_access.get_read_permit().await; // the ack is sent back to ourselves (and then ignored) - let topology_ref_option = topology_permit.try_get_valid_topology_ref( + let topology_ref = match topology_permit.try_get_valid_topology_ref( &self.our_full_destination, Some(&self.our_full_destination), - ); - if topology_ref_option.is_none() { - warn!( - "No valid topology detected - won't send any loop cover message this time" - ); - return; - } - let topology_ref = topology_ref_option.unwrap(); + ) { + Ok(topology) => topology, + Err(err) => { + warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"); + return; + } + }; ( generate_loop_cover_packet( @@ -487,7 +497,14 @@ where log::warn!( "Unable to send packets fast enough - sending delay multiplier set to: {}", self.sending_delay_controller.current_multiplier() - ); + ) + }; + if packets > 1000 { + log::warn!("{status_str}"); + } else if packets > 0 { + log::info!("{status_str}"); + } else { + log::debug!("{status_str}"); } } diff --git a/clients/client-core/src/client/received_buffer.rs b/clients/client-core/src/client/received_buffer.rs index fa61b9b9a0..36b5bbc3c5 100644 --- a/clients/client-core/src/client/received_buffer.rs +++ b/clients/client-core/src/client/received_buffer.rs @@ -1,26 +1,26 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::client::replies::reply_controller::ReplyControllerSender; +use crate::client::replies::reply_storage::SentReplyKeys; use crate::spawn_future; use crypto::asymmetric::encryption; +use crypto::Digest; use futures::channel::mpsc; use futures::lock::Mutex; use futures::StreamExt; use gateway_client::MixnetMessageReceiver; use log::*; +use nymsphinx::anonymous_replies::requests::{ + RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent, +}; +use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey}; +use nymsphinx::message::{NymMessage, PlainMessage}; +use nymsphinx::params::ReplySurbKeyDigestAlgorithm; use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage}; use std::collections::HashSet; use std::sync::Arc; -#[cfg(feature = "reply-surb")] -use crate::client::reply_key_storage::ReplyKeyStorage; -#[cfg(feature = "reply-surb")] -use crypto::{symmetric::stream_cipher, Digest}; -#[cfg(feature = "reply-surb")] -use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey}; -#[cfg(feature = "reply-surb")] -use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm}; - // Buffer Requests to say "hey, send any reconstructed messages to this channel" // or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead" pub type ReceivedBufferRequestSender = mpsc::UnboundedSender; @@ -46,24 +46,13 @@ struct ReceivedMessagesBufferInner { } impl ReceivedMessagesBufferInner { - fn process_received_fragment(&mut self, raw_fragment: Vec) -> Option { - let fragment_data = match self - .message_receiver - .recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment) - { - Err(e) => { - warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e); - return None; - } - Ok(frag_data) => frag_data, - }; - - if nymsphinx::cover::is_cover(&fragment_data) { + fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option { + if nymsphinx::cover::is_cover(fragment_data) { trace!("The message was a loop cover message! Skipping it"); return None; } - let fragment = match self.message_receiver.recover_fragment(&fragment_data) { + let fragment = match self.message_receiver.recover_fragment(fragment_data) { Err(e) => { warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e); return None; @@ -79,9 +68,10 @@ impl ReceivedMessagesBufferInner { // if we returned an error the underlying message is malformed in some way match self.message_receiver.insert_new_fragment(fragment) { Err(err) => match err { - MessageRecoveryError::MalformedReconstructedMessage(message_sets) => { + MessageRecoveryError::MalformedReconstructedMessage { source, used_sets } => { + error!("message reconstruction failed - {source}. Attempting to re-use the message sets..."); // TODO: should we really insert reconstructed sets? could this be abused for some attack? - for set_id in message_sets { + for set_id in used_sets { if !self.recently_reconstructed.insert(set_id) { // or perhaps we should even panic at this point? error!("Reconstructed another message containing already used set id!") @@ -107,6 +97,34 @@ impl ReceivedMessagesBufferInner { }, } } + + fn process_received_reply( + &mut self, + reply_ciphertext: &mut [u8], + reply_key: SurbEncryptionKey, + ) -> Option { + // note: this performs decryption IN PLACE without extra allocation + self.message_receiver + .recover_plaintext_from_reply(reply_ciphertext, reply_key); + let fragment_data = reply_ciphertext; + + self.recover_from_fragment(fragment_data) + } + + fn process_received_regular_packet(&mut self, mut raw_fragment: Vec) -> Option { + let fragment_data = match self.message_receiver.recover_plaintext_from_regular_packet( + self.local_encryption_keypair.private_key(), + &mut raw_fragment, + ) { + Err(e) => { + warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e); + return None; + } + Ok(frag_data) => frag_data, + }; + + self.recover_from_fragment(fragment_data) + } } #[derive(Debug, Clone)] @@ -114,17 +132,21 @@ impl ReceivedMessagesBufferInner { // You should always use .clone() to create additional instances struct ReceivedMessagesBuffer { inner: Arc>, - - /// Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back. + // Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back. // There's no need to put it behind a Mutex since it's already properly concurrent - #[cfg(feature = "reply-surb")] - reply_key_storage: ReplyKeyStorage, + // #[cfg(feature = "reply-surb")] + // reply_key_storage: ReplyKeyStorage, + + // + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, } impl ReceivedMessagesBuffer { fn new( local_encryption_keypair: Arc, - #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, ) -> Self { ReceivedMessagesBuffer { inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner { @@ -134,8 +156,8 @@ impl ReceivedMessagesBuffer { message_sender: None, recently_reconstructed: HashSet::new(), })), - #[cfg(feature = "reply-surb")] reply_key_storage, + reply_controller_sender, } } @@ -177,36 +199,141 @@ impl ReceivedMessagesBuffer { guard.message_sender = Some(sender); } - async fn add_reconstructed_messages(&mut self, msgs: Vec) { - debug!("Adding {:?} new messages to the buffer!", msgs.len()); - trace!("Adding new messages to the buffer! {:?}", msgs); - self.inner.lock().await.messages.extend(msgs) + fn handle_reconstructed_plain_messages( + &mut self, + msgs: Vec, + ) -> Vec { + msgs.into_iter().map(Into::into).collect() } - #[cfg(feature = "reply-surb")] - fn process_received_reply( - reply_ciphertext: &[u8], - reply_key: SurbEncryptionKey, - ) -> Option { - let zero_iv = stream_cipher::zero_iv::(); + fn handle_reconstructed_repliable_messages( + &mut self, + msgs: Vec, + ) -> Vec { + let mut reconstructed = Vec::new(); + for msg in msgs { + let (reply_surbs, from_surb_request) = match msg.content { + RepliableMessageContent::Data { + message, + reply_surbs, + } => { + trace!( + "received message that also contained additional {} reply surbs from {:?}!", + reply_surbs.len(), + msg.sender_tag + ); + + reconstructed.push(ReconstructedMessage::new(message, msg.sender_tag)); + + (reply_surbs, false) + } + RepliableMessageContent::AdditionalSurbs { reply_surbs } => { + trace!( + "received additional {} reply surbs from {:?}!", + reply_surbs.len(), + msg.sender_tag + ); + (reply_surbs, true) + } + RepliableMessageContent::Heartbeat { + additional_reply_surbs, + } => { + error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)"); + (additional_reply_surbs, false) + } + }; + + self.reply_controller_sender.send_additional_surbs( + msg.sender_tag, + reply_surbs, + from_surb_request, + ) + } + reconstructed + } - let mut reply_msg = stream_cipher::decrypt::( - reply_key.inner(), - &zero_iv, - reply_ciphertext, + fn handle_reconstructed_reply_messages( + &mut self, + msgs: Vec, + ) -> Vec { + let mut reconstructed = Vec::new(); + for msg in msgs { + match msg.content { + ReplyMessageContent::Data { message } => reconstructed.push(message.into()), + ReplyMessageContent::SurbRequest { recipient, amount } => { + debug!("received request for {amount} additional reply SURBs from {recipient}"); + self.reply_controller_sender + .send_additional_surbs_request(*recipient, amount); + } + } + } + reconstructed + } + + async fn handle_reconstructed_messages(&mut self, msgs: Vec) { + if msgs.is_empty() { + return; + } + + let mut plain_messages = Vec::new(); + let mut repliable_messages = Vec::new(); + let mut reply_messages = Vec::new(); + + for msg in msgs { + match msg { + NymMessage::Plain(plain) => plain_messages.push(plain), + NymMessage::Repliable(repliable) => repliable_messages.push(repliable), + NymMessage::Reply(reply) => reply_messages.push(reply), + } + } + + let mut reconstructed_messages = self.handle_reconstructed_plain_messages(plain_messages); + reconstructed_messages + .append(&mut self.handle_reconstructed_repliable_messages(repliable_messages)); + reconstructed_messages + .append(&mut self.handle_reconstructed_reply_messages(reply_messages)); + + let mut inner_guard = self.inner.lock().await; + debug!( + "Adding {:?} new messages to the buffer!", + reconstructed_messages.len() ); - if let Err(err) = MessageReceiver::remove_padding(&mut reply_msg) { - warn!("Received reply had malformed padding! - {:?}", err); - None + + if let Some(sender) = &inner_guard.message_sender { + trace!("Sending reconstructed messages to announced sender"); + if let Err(err) = sender.unbounded_send(reconstructed_messages) { + warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {:?})", err); + inner_guard.message_sender = None; + inner_guard.messages.extend(err.into_inner()); + } } else { - // TODO: perhaps having to say it doesn't have a surb an indication the type should be changed? - Some(ReconstructedMessage { - message: reply_msg, - reply_surb: None, - }) + trace!("No sender available - buffering reconstructed messages"); + inner_guard.messages.extend(reconstructed_messages) } } + // this function doesn't really belong here... + fn get_reply_key<'a>( + &self, + raw_message: &'a mut [u8], + ) -> Option<(SurbEncryptionKey, &'a mut [u8])> { + let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size(); + if raw_message.len() < reply_surb_digest_size { + return None; + } + + let possible_key_digest = + EncryptionKeyDigest::clone_from_slice(&raw_message[..reply_surb_digest_size]); + self.reply_key_storage + .try_pop(possible_key_digest) + .map(|reply_encryption_key| { + ( + reply_encryption_key, + &mut raw_message[reply_surb_digest_size..], + ) + }) + } + async fn handle_new_received(&mut self, msgs: Vec>) { trace!( "Processing {:?} new message that might get added to the buffer!", @@ -217,69 +344,27 @@ impl ReceivedMessagesBuffer { let mut inner_guard = self.inner.lock().await; // first check if this is a reply or a chunked message - // TODO: verify with @AP if this way of doing it is safe or whether it could - // cause some attacks due to, I don't know, stupid edge case collisions? - // Update: this DOES introduce a possible leakage: https://github.com/nymtech/nym/issues/296 - for msg in msgs { - // TODO: - // 1. make it nicer - // 2. make it not feature-locked - - #[cfg(feature = "reply-surb")] - { - let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size(); - - let possible_key_digest = - EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]); - - // check first `HasherOutputSize` bytes if they correspond to known encryption key - // if yes - this is a reply message - - // TODO: this might be a bottleneck - since the keys are stored on disk we, presumably, - // are doing a disk operation every single received fragment - if let Some(reply_encryption_key) = self - .reply_key_storage - .get_and_remove_encryption_key(possible_key_digest) - .expect("storage operation failed!") - { - if let Some(completed_message) = Self::process_received_reply( - &msg[reply_surb_digest_size..], - reply_encryption_key, - ) { - completed_messages.push(completed_message) - } + // note: there's a possible information leakage associated with this check https://github.com/nymtech/nym/issues/296 + for mut msg in msgs { + // check first `HasherOutputSize` bytes if they correspond to known encryption key + // if yes - this is a reply message + let completed_message = + if let Some((reply_key, reply_message)) = self.get_reply_key(&mut msg) { + inner_guard.process_received_reply(reply_message, reply_key) } else { - // otherwise - it's a 'normal' message - if let Some(completed_message) = inner_guard.process_received_fragment(msg) { - completed_messages.push(completed_message) - } - } - } + inner_guard.process_received_regular_packet(msg) + }; - #[cfg(not(feature = "reply-surb"))] - if let Some(completed_message) = inner_guard.process_received_fragment(msg) { - completed_messages.push(completed_message) + if let Some(completed) = completed_message { + info!("received {completed}"); + completed_messages.push(completed) } } + drop(inner_guard); + if !completed_messages.is_empty() { - if let Some(sender) = &inner_guard.message_sender { - trace!("Sending reconstructed messages to announced sender"); - if let Err(err) = sender.unbounded_send(completed_messages) { - warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {:?})", err); - // make sure to drop the lock to not deadlock - // (it is required by `add_reconstructed_messages`) - inner_guard.message_sender = None; - drop(inner_guard); - self.add_reconstructed_messages(err.into_inner()).await; - } - } else { - // make sure to drop the lock to not deadlock - // (it is required by `add_reconstructed_messages`) - drop(inner_guard); - trace!("No sender available - buffering reconstructed messages"); - self.add_reconstructed_messages(completed_messages).await; - } + self.handle_reconstructed_messages(completed_messages).await } } } @@ -413,12 +498,13 @@ impl ReceivedMessagesBufferController { local_encryption_keypair: Arc, query_receiver: ReceivedBufferRequestReceiver, mixnet_packet_receiver: MixnetMessageReceiver, - #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, ) -> Self { let received_buffer = ReceivedMessagesBuffer::new( local_encryption_keypair, - #[cfg(feature = "reply-surb")] reply_key_storage, + reply_controller_sender, ); ReceivedMessagesBufferController { diff --git a/clients/client-core/src/client/replies/mod.rs b/clients/client-core/src/client/replies/mod.rs new file mode 100644 index 0000000000..61b5f19abf --- /dev/null +++ b/clients/client-core/src/client/replies/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod reply_controller; +pub mod reply_storage; diff --git a/clients/client-core/src/client/replies/reply_controller.rs b/clients/client-core/src/client/replies/reply_controller.rs new file mode 100644 index 0000000000..30b74c0118 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_controller.rs @@ -0,0 +1,519 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError}; +use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, UsedSenderTags}; +use client_connections::TransmissionLane; +use futures::channel::mpsc; +use futures::StreamExt; +use log::{debug, trace, warn}; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::chunking::fragment::Fragment; +use rand::{CryptoRng, Rng}; +use std::cmp::{max, min}; +use std::collections::{HashMap, VecDeque}; +use std::time::Duration; +use tokio::time::Instant; + +pub fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) { + let (tx, rx) = mpsc::unbounded(); + (tx.into(), rx) +} + +#[derive(Debug, Clone)] +pub struct ReplyControllerSender(mpsc::UnboundedSender); + +impl From> for ReplyControllerSender { + fn from(inner: mpsc::UnboundedSender) -> Self { + ReplyControllerSender(inner) + } +} + +impl ReplyControllerSender { + pub(crate) fn send_reply( + &self, + recipient: AnonymousSenderTag, + message: Vec, + lane: TransmissionLane, + ) { + self.0 + .unbounded_send(ReplyControllerMessage::SendReply { + recipient, + message, + lane, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub(crate) fn send_additional_surbs( + &self, + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + ) { + self.0 + .unbounded_send(ReplyControllerMessage::AdditionalSurbs { + sender_tag, + reply_surbs, + from_surb_request, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) { + self.0 + .unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest { + recipient: Box::new(recipient), + amount, + }) + .expect("ReplyControllerReceiver has died!") + } +} + +pub type ReplyControllerReceiver = mpsc::UnboundedReceiver; + +#[derive(Debug)] +pub enum ReplyControllerMessage { + SendReply { + recipient: AnonymousSenderTag, + message: Vec, + lane: TransmissionLane, + }, + + AdditionalSurbs { + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + }, + + // Should this also be handled in here? it's technically a completely different side of the pipe + // let's see how it works when combined, might split it before creating PR + AdditionalSurbsRequest { + recipient: Box, + amount: u32, + }, +} + +// the purpose of this task: +// - buffers split messages from input message listener if there were insufficient surbs to send them +// - upon getting extra surbs, resends them +// - so I guess it will handle all 'RepliableMessage' and requests from 'ReplyMessage' +// - replies to "give additional surbs" requests +// - will reply to future heartbeats + +// TODO: this should be split into ingress and egress controllers +// because currently its trying to perform two distinct jobs +pub struct ReplyController { + // TODO: incorporate that field at some point + // and use binomial distribution to determine the expected required number + // of surbs required to send the message through + // expected_reliability: f32, + request_receiver: ReplyControllerReceiver, + pending_replies: HashMap>, + message_handler: MessageHandler, + received_reply_surbs: ReceivedReplySurbsMap, + tag_storage: UsedSenderTags, + + min_surb_request_size: u32, + max_surb_request_size: u32, + maximum_allowed_reply_surb_request_size: u32, + max_surb_waiting_period: Duration, +} + +impl ReplyController +where + R: CryptoRng + Rng, +{ + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + message_handler: MessageHandler, + received_reply_surbs: ReceivedReplySurbsMap, + tag_storage: UsedSenderTags, + request_receiver: ReplyControllerReceiver, + min_surb_request_size: u32, + max_surb_request_size: u32, + maximum_allowed_reply_surb_request_size: u32, + max_surb_waiting_period: Duration, + ) -> Self { + ReplyController { + request_receiver, + pending_replies: Default::default(), + message_handler, + received_reply_surbs, + tag_storage, + min_surb_request_size, + max_surb_request_size, + maximum_allowed_reply_surb_request_size, + max_surb_waiting_period, + } + } + + fn insert_pending_replies(&mut self, recipient: &AnonymousSenderTag, fragments: Vec) { + if let Some(existing) = self.pending_replies.get_mut(recipient) { + existing.append(&mut fragments.into()) + } else { + self.pending_replies.insert(*recipient, fragments.into()); + } + } + + fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool { + trace!("checking if we should request more surbs from {:?}", target); + + // if we don't have any information associated with this target, + // then we definitely don't want any more surbs + let queue_size = match self.pending_replies.get(target) { + Some(pending_queue) => pending_queue.len(), + None => return false, + }; + + let available_surbs = self.received_reply_surbs.available_surbs(target); + let pending_surbs = self.received_reply_surbs.pending_reception(target) as usize; + let min_surbs_threshold = self.received_reply_surbs.min_surb_threshold(); + let max_surbs_threshold = self.received_reply_surbs.max_surb_threshold(); + + debug!("queue size: {queue_size}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}"); + + (pending_surbs + available_surbs) < max_surbs_threshold + && (pending_surbs + available_surbs) < (queue_size + min_surbs_threshold) + } + + async fn handle_send_reply( + &mut self, + recipient_tag: AnonymousSenderTag, + data: Vec, + lane: TransmissionLane, + ) { + if !self.received_reply_surbs.contains_surbs_for(&recipient_tag) { + warn!("received reply request for {:?} but we don't have any surbs stored for that recipient!", recipient_tag); + return; + } + + trace!("handling reply to {:?}", recipient_tag); + let fragments = self.message_handler.split_reply_message(data); + + let required_surbs = fragments.len(); + trace!("This reply requires {:?} SURBs", required_surbs); + + // TODO: edge case: + // we're making a lot of requests and have to request a lot of surbs + // (but at some point we run out of surbs for surb requests) + + let (surbs, _surbs_left) = self + .received_reply_surbs + .get_reply_surbs(&recipient_tag, required_surbs); + + if let Some(reply_surbs) = surbs { + if let Err(err) = self + .message_handler + .try_send_reply_chunks(recipient_tag, fragments, reply_surbs, lane) + .await + { + // TODO: perhaps there should be some timer here to repeat the request once topology recovers + let err = err.return_unused_surbs(&self.received_reply_surbs, &recipient_tag); + warn!("failed to send reply to {:?} - {err}", recipient_tag); + } + } else { + // we don't have enough surbs for this reply + self.insert_pending_replies(&recipient_tag, fragments); + + if self.should_request_more_surbs(&recipient_tag) { + self.request_reply_surbs_for_queue_clearing(recipient_tag) + .await; + } + } + } + + async fn request_additional_reply_surbs( + &mut self, + target: AnonymousSenderTag, + amount: u32, + ) -> Result<(), PreparationError> { + let reply_surb = self + .received_reply_surbs + .get_reply_surb_ignoring_threshold(&target) + .and_then(|(reply_surb, _)| reply_surb) + .ok_or(PreparationError::NotEnoughSurbs { + available: 0, + required: 1, + })?; + + if let Err(err) = self + .message_handler + .try_request_additional_reply_surbs(target, reply_surb, amount) + .await + { + let err = err.return_unused_surbs(&self.received_reply_surbs, &target); + warn!( + "failed to request additional surbs from {:?} - {err}", + target + ); + // TODO: perhaps there should be some timer here to repeat the request once topology recovers + return Err(err); + } else { + self.received_reply_surbs + .increment_pending_reception(&target, amount); + } + + Ok(()) + } + + fn pop_at_most_pending_replies( + &mut self, + from: &AnonymousSenderTag, + amount: usize, + ) -> Option> { + // if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb + let total = self.pending_replies.get(from)?.len(); + trace!("pending queue has {total} elements"); + if total == 0 { + return None; + } + if total < amount { + self.pending_replies.remove(from) + } else { + Some( + self.pending_replies + .get_mut(from)? + .drain(..amount) + .collect(), + ) + } + } + + async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) { + trace!("trying to clear pending queue"); + let available_surbs = self.received_reply_surbs.available_surbs(&target); + let min_surbs_threshold = self.received_reply_surbs.min_surb_threshold(); + + let max_to_clear = if available_surbs > min_surbs_threshold { + available_surbs - min_surbs_threshold + } else { + trace!("we don't have enough surbs for queue clearing..."); + return; + }; + trace!("we can clear up to {max_to_clear} entries"); + + // we're guaranteed to not get more entries than we have reply surbs for + if let Some(to_send) = self.pop_at_most_pending_replies(&target, max_to_clear) { + // TODO: optimise: we're cloning the fragments every time to re-insert them into the buffer in case of failure + let to_send_vec = to_send.into_iter().collect::>(); + + if to_send_vec.is_empty() { + panic!( + "please let the devs know if you ever see this message (reply_controller.rs)" + ); + } + + let (surbs_for_reply, _) = self + .received_reply_surbs + .get_reply_surbs(&target, to_send_vec.len()); + let surbs_for_reply = + surbs_for_reply.expect("is is possible for this to ever show up?"); + + if let Err(err) = self + .message_handler + .try_send_reply_chunks( + target, + to_send_vec, + surbs_for_reply, + TransmissionLane::General, + ) + .await + { + let err = err.return_unused_surbs(&self.received_reply_surbs, &target); + warn!("failed to clear pending queue for {:?} - {err}", target); + // TODO: perhaps there should be some timer here to repeat the request once topology recovers + } + } else { + trace!("the pending queue is empty"); + } + } + + async fn handle_received_surbs( + &mut self, + from: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + ) { + trace!("handling received surbs"); + + // clear the requesting flag since we should have been asking for surbs + self.received_reply_surbs + .reset_surbs_last_received_at(&from); + if from_surb_request { + self.received_reply_surbs + .decrement_pending_reception(&from, reply_surbs.len() as u32); + } + + // store received surbs + self.received_reply_surbs.insert_surbs(&from, reply_surbs); + + // use as many as we can for clearing pending queue + self.try_clear_pending_queue(from).await; + + // if we have to, request more + if self.should_request_more_surbs(&from) { + self.request_reply_surbs_for_queue_clearing(from).await; + } + } + + async fn handle_surb_request(&mut self, recipient: Recipient, mut amount: u32) { + // 1. check whether we sent any surbs in the past to this recipient, otherwise + // they have no business in asking for more + if !self.tag_storage.exists(&recipient) { + warn!("{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!"); + return; + } + + // 2. check whether the requested amount is within sane range + if amount > self.maximum_allowed_reply_surb_request_size { + warn!("The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...", self.maximum_allowed_reply_surb_request_size); + amount = self.maximum_allowed_reply_surb_request_size; + } + + // 3. construct and send the surbs away + // (send them in smaller batches to make the experience a bit smoother + let mut remaining = amount; + while remaining > 0 { + let to_send = min(remaining, 100); + if let Err(err) = self + .message_handler + .try_send_additional_reply_surbs(recipient, to_send) + .await + { + warn!("failed to send additional surbs to {recipient} - {err}"); + } else { + trace!("sent {to_send} reply SURBs to {recipient}"); + } + + remaining -= to_send; + } + } + + async fn handle_request(&mut self, request: ReplyControllerMessage) { + match request { + ReplyControllerMessage::SendReply { + recipient, + message, + lane, + } => self.handle_send_reply(recipient, message, lane).await, + ReplyControllerMessage::AdditionalSurbs { + sender_tag, + reply_surbs, + from_surb_request, + } => { + self.handle_received_surbs(sender_tag, reply_surbs, from_surb_request) + .await + } + ReplyControllerMessage::AdditionalSurbsRequest { recipient, amount } => { + self.handle_surb_request(*recipient, amount).await + } + } + } + + async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) { + trace!("requesting surbs for queue clearing"); + + let pending = match self.pending_replies.get(&target) { + Some(pending) => pending, + None => { + warn!("there are no pending replies for {:?}!", target); + return; + } + }; + let queue_size = pending.len() as u32; + if queue_size == 0 { + trace!("the pending queue for {:?} is already empty", target); + return; + } + + let request_size = min( + self.max_surb_request_size, + max(queue_size, self.min_surb_request_size), + ); + + if let Err(err) = self + .request_additional_reply_surbs(target, request_size) + .await + { + warn!("failed to request additional surbs... - {err}") + } + } + + async fn inspect_stale_entries(&mut self) { + let mut to_request = Vec::new(); + + let now = Instant::now(); + for (pending_reply_target, vals) in &self.pending_replies { + if vals.is_empty() { + // TODO: remove it from the map before getting here + continue; + } + + let last_received = self + .received_reply_surbs + .surbs_last_received_at(pending_reply_target) + .expect("I think this shouldnt fail? to be verified."); + + let diff = now - last_received; + + if diff > self.max_surb_waiting_period { + warn!("We haven't received any surbs in {:?} from {:?}. Going to explicitly ask for more", diff, pending_reply_target); + to_request.push(*pending_reply_target); + } + } + + for pending_reply_target in to_request { + self.request_reply_surbs_for_queue_clearing(pending_reply_target) + .await; + self.received_reply_surbs + .reset_pending_reception(&pending_reply_target) + } + } + + #[cfg(not(target_arch = "wasm32"))] + pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + debug!("Started ReplyController with graceful shutdown support"); + + let polling_rate = Duration::from_secs(5); + let mut interval_timer = tokio::time::interval(polling_rate); + + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + log::trace!("ReplyController: Received shutdown"); + }, + req = self.request_receiver.next() => match req { + Some(req) => self.handle_request(req).await, + None => { + log::trace!("ReplyController: Stopping since channel closed"); + break; + } + }, + _ = interval_timer.tick() => { + self.inspect_stale_entries().await + }, + } + } + assert!(shutdown.is_shutdown_poll()); + log::debug!("ReplyController: Exiting"); + } + + #[cfg(target_arch = "wasm32")] + pub(crate) async fn run(&mut self) { + debug!("Started ReplyController without graceful shutdown support"); + + let polling_rate = Duration::from_secs(5); + let mut interval_timer = tokio::time::interval(polling_rate); + + loop { + tokio::select! { + req = self.request_receiver.next() => self.handle_request(req.unwrap()).await, + _ = interval_timer.tick() => self.inspect_stale_entries().await + } + } + } +} diff --git a/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs new file mode 100644 index 0000000000..87d5c392c8 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs @@ -0,0 +1,2 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 diff --git a/clients/client-core/src/client/reply_key_storage.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend.rs similarity index 98% rename from clients/client-core/src/client/reply_key_storage.rs rename to clients/client-core/src/client/replies/reply_storage/backend/fs_backend.rs index bfe1c43b9b..7dc75e61cb 100644 --- a/clients/client-core/src/client/reply_key_storage.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crypto::generic_array::typenum::Unsigned; diff --git a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs new file mode 100644 index 0000000000..41313bd5a7 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nymsphinx::anonymous_replies::SurbEncryptionKey; + +#[cfg(target_arch = "wasm32")] +mod browser_backend; + +#[cfg(not(target_arch = "wasm32"))] +mod fs_backend; + +// implemented via trait as implementations are going to vary wildly between wasm and non-wasm targets +// TODO: might need to be transformed into an async-trait. not sure yet +trait ReplyStorageBackend { + type StorageError; + + fn insert_encryption_key(&mut self, key: SurbEncryptionKey) -> Result<(), Self::StorageError>; +} diff --git a/clients/client-core/src/client/replies/reply_storage/combined.rs b/clients/client-core/src/client/replies/reply_storage/combined.rs new file mode 100644 index 0000000000..2862a0e14b --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/combined.rs @@ -0,0 +1,36 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags}; + +#[derive(Debug, Clone)] +pub struct CombinedReplyStorage { + sent_reply_keys: SentReplyKeys, + received_reply_surbs: ReceivedReplySurbsMap, + used_tags: UsedSenderTags, +} + +impl CombinedReplyStorage { + pub fn new(min_surb_threshold: usize, max_surb_threshold: usize) -> CombinedReplyStorage { + CombinedReplyStorage { + sent_reply_keys: SentReplyKeys::new(), + received_reply_surbs: ReceivedReplySurbsMap::new( + min_surb_threshold, + max_surb_threshold, + ), + used_tags: UsedSenderTags::new(), + } + } + + pub fn key_storage(&self) -> SentReplyKeys { + self.sent_reply_keys.clone() + } + + pub fn surbs_storage(&self) -> ReceivedReplySurbsMap { + self.received_reply_surbs.clone() + } + + pub fn tags_storage(&self) -> UsedSenderTags { + self.used_tags.clone() + } +} diff --git a/clients/client-core/src/client/replies/reply_storage/key_storage.rs b/clients/client-core/src/client/replies/reply_storage/key_storage.rs new file mode 100644 index 0000000000..e381233d7f --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/key_storage.rs @@ -0,0 +1,43 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use dashmap::DashMap; +use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest; +use nymsphinx::anonymous_replies::SurbEncryptionKey; +use std::sync::Arc; + +#[derive(Debug, Clone)] +// TODO: we might have to also put the tag here +// TODO2: some timestamp to indicate when entries should get purged if we expect to never get the reply back +pub struct SentReplyKeys { + inner: Arc, +} + +#[derive(Debug)] +struct SentReplyKeysInner { + data: DashMap, +} + +impl SentReplyKeys { + pub(crate) fn new() -> SentReplyKeys { + SentReplyKeys { + inner: Arc::new(SentReplyKeysInner { + data: DashMap::new(), + }), + } + } + + pub(crate) fn insert_multiple(&self, keys: Vec) { + for key in keys { + self.insert(key) + } + } + + pub(crate) fn insert(&self, key: SurbEncryptionKey) { + self.inner.data.insert(key.compute_digest(), key); + } + + pub(crate) fn try_pop(&self, digest: EncryptionKeyDigest) -> Option { + self.inner.data.remove(&digest).map(|(_k, v)| v) + } +} diff --git a/clients/client-core/src/client/replies/reply_storage/mod.rs b/clients/client-core/src/client/replies/reply_storage/mod.rs new file mode 100644 index 0000000000..eb265c58cc --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/mod.rs @@ -0,0 +1,34 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use crate::client::replies::reply_storage::combined::CombinedReplyStorage; +pub use crate::client::replies::reply_storage::key_storage::SentReplyKeys; +pub use crate::client::replies::reply_storage::surb_storage::ReceivedReplySurbsMap; +pub use crate::client::replies::reply_storage::tag_storage::UsedSenderTags; + +mod combined; +mod key_storage; +mod surb_storage; +mod tag_storage; + +// TODO: TO BE DEALT WITH IN ANOTHER PR +// TODO: TO BE DEALT WITH IN ANOTHER PR +// TODO: TO BE DEALT WITH IN ANOTHER PR +// mod backend; +// +// // only really exists to get information about shutdown and save data to the backing storage +// pub struct ReplyStorage { +// combined_storage: CombinedReplyStorage, +// backend: T, +// } +// +// impl Drop for ReplyStorage { +// fn drop(&mut self) { +// println!("REPLY STORAGE IS GETTING DROPPED - WE SHOULD FLUSH ALL OUR DATA TO THE BACKING STORAGE!!") +// // todo!("flush everything to backend storage") +// } +// } +// +// impl ReplyStorage { +// +// } diff --git a/clients/client-core/src/client/replies/reply_storage/surb_storage.rs b/clients/client-core/src/client/replies/reply_storage/surb_storage.rs new file mode 100644 index 0000000000..1a5d102765 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/surb_storage.rs @@ -0,0 +1,237 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use dashmap::DashMap; +use log::trace; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nymsphinx::anonymous_replies::ReplySurb; +use std::collections::VecDeque; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use tokio::time::Instant; + +#[derive(Debug, Clone)] +pub struct ReceivedReplySurbsMap { + inner: Arc, +} + +#[derive(Debug)] +struct ReceivedReplySurbsMapInner { + data: DashMap, + + // the minimum amount of surbs that have to be kept in storage for requests for more surbs + min_surb_threshold: AtomicUsize, + + // the maximum amount of surbs that we want to keep in storage so that we don't over-request them + max_surb_threshold: AtomicUsize, +} + +impl ReceivedReplySurbsMap { + pub(crate) fn new( + min_surb_threshold: usize, + max_surb_threshold: usize, + ) -> ReceivedReplySurbsMap { + ReceivedReplySurbsMap { + inner: Arc::new(ReceivedReplySurbsMapInner { + data: DashMap::new(), + min_surb_threshold: AtomicUsize::new(min_surb_threshold), + max_surb_threshold: AtomicUsize::new(max_surb_threshold), + }), + } + } + + pub(crate) fn reset_surbs_last_received_at(&self, target: &AnonymousSenderTag) { + if let Some(mut entry) = self.inner.data.get_mut(target) { + entry.surbs_last_received_at = Instant::now(); + } + } + + pub(crate) fn surbs_last_received_at(&self, target: &AnonymousSenderTag) -> Option { + self.inner + .data + .get(target) + .map(|e| e.surbs_last_received_at()) + } + + pub(crate) fn pending_reception(&self, target: &AnonymousSenderTag) -> u32 { + self.inner + .data + .get(target) + .map(|e| e.pending_reception()) + .unwrap_or_default() + } + + pub(crate) fn increment_pending_reception( + &self, + target: &AnonymousSenderTag, + amount: u32, + ) -> Option { + self.inner + .data + .get_mut(target) + .map(|mut e| e.increment_pending_reception(amount)) + } + + pub(crate) fn decrement_pending_reception( + &self, + target: &AnonymousSenderTag, + amount: u32, + ) -> Option { + self.inner + .data + .get_mut(target) + .map(|mut e| e.decrement_pending_reception(amount)) + } + + pub(crate) fn reset_pending_reception(&self, target: &AnonymousSenderTag) { + if let Some(mut e) = self.inner.data.get_mut(target) { + e.reset_pending_reception() + } + } + + pub(crate) fn min_surb_threshold(&self) -> usize { + self.inner.min_surb_threshold.load(Ordering::Relaxed) + } + + pub(crate) fn max_surb_threshold(&self) -> usize { + self.inner.max_surb_threshold.load(Ordering::Relaxed) + } + + pub(crate) fn available_surbs(&self, target: &AnonymousSenderTag) -> usize { + self.inner + .data + .get(target) + .map(|entry| entry.items_left()) + .unwrap_or_default() + } + + pub(crate) fn contains_surbs_for(&self, target: &AnonymousSenderTag) -> bool { + self.inner.data.contains_key(target) + } + + pub(crate) fn get_reply_surbs( + &self, + target: &AnonymousSenderTag, + amount: usize, + ) -> (Option>, usize) { + if let Some(mut entry) = self.inner.data.get_mut(target) { + let surbs_left = entry.items_left(); + if surbs_left < self.min_surb_threshold() + amount { + (None, surbs_left) + } else { + entry.get_reply_surbs(amount) + } + } else { + (None, 0) + } + } + + pub(crate) fn get_reply_surb_ignoring_threshold( + &self, + target: &AnonymousSenderTag, + ) -> Option<(Option, usize)> { + self.inner + .data + .get_mut(target) + .map(|mut s| s.get_reply_surb()) + } + + pub(crate) fn get_reply_surb( + &self, + target: &AnonymousSenderTag, + ) -> Option<(Option, usize)> { + self.inner.data.get_mut(target).map(|mut entry| { + let surbs_left = entry.items_left(); + if surbs_left < self.min_surb_threshold() { + (None, surbs_left) + } else { + entry.get_reply_surb() + } + }) + } + + pub(crate) fn insert_surbs>( + &self, + target: &AnonymousSenderTag, + surbs: I, + ) { + if let Some(mut existing_data) = self.inner.data.get_mut(target) { + existing_data.insert_reply_surbs(surbs) + } else { + let new_entry = ReceivedReplySurbs::new(surbs.into_iter().collect()); + self.inner.data.insert(*target, new_entry); + } + } +} + +#[derive(Debug)] +struct ReceivedReplySurbs { + // in the future we'd probably want to put extra data here to indicate when the SURBs got received + // so we could invalidate entries from the previous key rotations + data: VecDeque, + + pending_reception: u32, + surbs_last_received_at: Instant, +} + +impl ReceivedReplySurbs { + fn new(initial_surbs: VecDeque) -> Self { + ReceivedReplySurbs { + data: initial_surbs, + pending_reception: 0, + surbs_last_received_at: Instant::now(), + } + } + + pub(crate) fn surbs_last_received_at(&self) -> Instant { + self.surbs_last_received_at + } + + pub(crate) fn pending_reception(&self) -> u32 { + self.pending_reception + } + + pub(crate) fn increment_pending_reception(&mut self, amount: u32) -> u32 { + self.pending_reception += amount; + self.pending_reception + } + + pub(crate) fn decrement_pending_reception(&mut self, amount: u32) -> u32 { + self.pending_reception = self.pending_reception.saturating_sub(amount); + self.pending_reception + } + + pub(crate) fn reset_pending_reception(&mut self) { + self.pending_reception = 0; + } + + pub(crate) fn get_reply_surbs(&mut self, amount: usize) -> (Option>, usize) { + if self.items_left() < amount { + (None, self.items_left()) + } else { + let surbs = self.data.drain(..amount).collect(); + (Some(surbs), self.items_left()) + } + } + + pub(crate) fn get_reply_surb(&mut self) -> (Option, usize) { + (self.pop_surb(), self.items_left()) + } + + fn pop_surb(&mut self) -> Option { + self.data.pop_front() + } + + fn items_left(&self) -> usize { + self.data.len() + } + + // realistically we're always going to be getting multiple surbs at once + pub(crate) fn insert_reply_surbs>(&mut self, surbs: I) { + let mut v = surbs.into_iter().collect::>(); + trace!("storing {} surbs in the storage", v.len()); + self.data.append(&mut v); + self.surbs_last_received_at = Instant::now(); + trace!("we now have {} surbs!", self.data.len()); + } +} diff --git a/clients/client-core/src/client/replies/reply_storage/tag_storage.rs b/clients/client-core/src/client/replies/reply_storage/tag_storage.rs new file mode 100644 index 0000000000..0651ec8807 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_storage/tag_storage.rs @@ -0,0 +1,42 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use dashmap::DashMap; +use nymsphinx::addressing::clients::{Recipient, RecipientBytes}; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct UsedSenderTags { + inner: Arc, +} + +#[derive(Debug)] +struct UsedSenderTagsInner { + data: DashMap, +} + +impl UsedSenderTags { + pub(crate) fn new() -> UsedSenderTags { + UsedSenderTags { + inner: Arc::new(UsedSenderTagsInner { + data: DashMap::new(), + }), + } + } + + pub(crate) fn insert_new(&self, recipient: &Recipient, tag: AnonymousSenderTag) { + self.inner.data.insert(recipient.to_bytes(), tag); + } + + pub(crate) fn try_get_existing(&self, recipient: &Recipient) -> Option { + self.inner + .data + .get(&recipient.to_bytes()) + .map(|r| *r.value()) + } + + pub(crate) fn exists(&self, recipient: &Recipient) -> bool { + self.inner.data.contains_key(&recipient.to_bytes()) + } +} diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index afedc7ad61..0299d58beb 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::spawn_future; @@ -11,10 +11,29 @@ use std::ops::Deref; use std::sync::Arc; use std::time; use std::time::Duration; +use thiserror::Error; use tokio::sync::{RwLock, RwLockReadGuard}; -use topology::{nym_topology_from_detailed, NymTopology}; +use topology::{nym_topology_from_detailed, MixLayer, NymTopology}; use url::Url; +#[derive(Debug, Clone, Error)] +pub enum InvalidTopologyError { + #[error("The provided network topology is empty - the network request(s) probably failed")] + EmptyNetworkTopology, + + #[error("The provided network topology has no gateways available")] + NoGatewaysAvailable, + + #[error("The provided network topology has no mixnodes available")] + NoMixnodesAvailable, + + #[error("{layer} has no mixnodes available")] + EmptyMixLayer { layer: MixLayer }, + + #[error("Gateway with identity key {identity_key} doesn't exist")] + NonExistentGatewayError { identity_key: String }, +} + // I'm extremely curious why compiler NEVER complained about lack of Debug here before #[derive(Debug)] pub struct TopologyAccessorInner(Option); @@ -54,18 +73,56 @@ impl<'a> TopologyReadPermit<'a> { &'a self, ack_recipient: &Recipient, packet_recipient: Option<&Recipient>, - ) -> Option<&'a NymTopology> { - // Note: implicit deref with Deref for TopologyReadPermit is happening here - let topology_ref_option = self.permit.as_ref(); - topology_ref_option.as_ref().filter(|topology_ref| { - !(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS) - || !topology_ref.gateway_exists(ack_recipient.gateway()) - || if let Some(packet_recipient) = packet_recipient { - !topology_ref.gateway_exists(packet_recipient.gateway()) - } else { - false - }) - }) + ) -> Result<&'a NymTopology, InvalidTopologyError> { + // 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through? + let topology = self + .permit + .as_ref() + .as_ref() + .ok_or(InvalidTopologyError::EmptyNetworkTopology)?; + + // note: `can_construct_path_through` is equivalent to #2, #3, #4 + + // 2. does it have any mixnode at all? + let mixnodes = topology.mixes(); + if mixnodes.is_empty() { + return Err(InvalidTopologyError::NoMixnodesAvailable); + } + + // 3. does it have any gateways at all? + if topology.gateways().is_empty() { + return Err(InvalidTopologyError::NoGatewaysAvailable); + } + + // 4. does it have a mixnode on each layer? + for layer in 1..=DEFAULT_NUM_MIX_HOPS { + match mixnodes.get(&layer) { + None => return Err(InvalidTopologyError::EmptyMixLayer { layer }), + Some(layer_nodes) => { + if layer_nodes.is_empty() { + return Err(InvalidTopologyError::EmptyMixLayer { layer }); + } + } + } + } + + // 5. does it contain OUR gateway (so that we could create an ack packet)? + if !topology.gateway_exists(ack_recipient.gateway()) { + return Err(InvalidTopologyError::NonExistentGatewayError { + identity_key: ack_recipient.gateway().to_base58_string(), + }); + } + + // 6. for our target recipient, does it contain THEIR gateway (so that we could create + if let Some(recipient) = packet_recipient { + if !topology.gateway_exists(recipient.gateway()) { + return Err(InvalidTopologyError::NonExistentGatewayError { + identity_key: recipient.gateway().to_base58_string(), + }); + } + } + + Ok(topology) } } @@ -243,7 +300,7 @@ impl TopologyRefresher { let mixnodes = match self.validator_client.get_cached_active_mixnodes().await { Err(err) => { - error!("failed to get network mixnodes - {}", err); + error!("failed to get network mixnodes - {err}"); return None; } Ok(mixes) => mixes, @@ -251,7 +308,7 @@ impl TopologyRefresher { let gateways = match self.validator_client.get_cached_gateways().await { Err(err) => { - error!("failed to get network gateways - {}", err); + error!("failed to get network gateways - {err}"); return None; } Ok(gateways) => gateways, diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 4a3d1c1273..904889e2c9 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -30,6 +30,23 @@ const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_00 // bandwidth bridging protocol, we can come back to a smaller timeout value const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); +// reply-surbs related: + +// define when to request +// clients/client-core/src/client/replies/reply_storage/surb_storage.rs +const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200; + +// define how much to request at once +// clients/client-core/src/client/replies/reply_controller.rs +const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; + +const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; + +const DEFAULT_RETRANSMISSION_REPLY_SURB_REQUEST_SIZE: u32 = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD: Duration = Duration::from_secs(10); + pub fn missing_string_value() -> String { MISSING_VALUE.to_string() } @@ -147,6 +164,10 @@ impl Config { self.client.id.clone() } + pub fn get_debug_config(&self) -> &Debug { + &self.debug + } + pub fn get_disabled_credentials_mode(&self) -> bool { self.client.disabled_credentials_mode } @@ -211,6 +232,10 @@ impl Config { self.client.database_path.clone() } + pub fn get_version(&self) -> &str { + &self.client.version + } + // Debug getters pub fn get_average_packet_delay(&self) -> Duration { self.debug.average_packet_delay @@ -257,11 +282,35 @@ impl Config { } pub fn get_use_extended_packet_size(&self) -> Option { - self.debug.use_extended_packet_size.clone() + self.debug.use_extended_packet_size } - pub fn get_version(&self) -> &str { - &self.client.version + pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize { + self.debug.minimum_reply_surb_storage_threshold + } + + pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize { + self.debug.maximum_reply_surb_storage_threshold + } + + pub fn get_minimum_reply_surb_request_size(&self) -> u32 { + self.debug.minimum_reply_surb_request_size + } + + pub fn get_maximum_reply_surb_request_size(&self) -> u32 { + self.debug.maximum_reply_surb_request_size + } + + pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 { + self.debug.maximum_allowed_reply_surb_request_size + } + + pub fn get_retransmission_reply_surb_request_size(&self) -> u32 { + self.debug.retransmission_reply_surb_request_size + } + + pub fn get_maximum_reply_surb_waiting_period(&self) -> Duration { + self.debug.maximum_reply_surb_waiting_period } } @@ -485,9 +534,33 @@ pub struct Debug { /// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size. pub use_extended_packet_size: Option, + + /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. + /// It can only allow to go below that value if its to request additional reply surbs. + pub minimum_reply_surb_storage_threshold: usize, + + /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. + pub maximum_reply_surb_storage_threshold: usize, + + /// Defines the minimum number of reply surbs the client would request. + pub minimum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs the client would request. + pub maximum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. + pub maximum_allowed_reply_surb_request_size: u32, + + /// Defines the amount of reply surbs that the client is going to request when it runs out while attempting to retransmit packets. + pub retransmission_reply_surb_request_size: u32, + + /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking + /// for more even though in theory they wouldn't need to. + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_waiting_period: Duration, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ExtendedPacketSize { Extended8, @@ -510,6 +583,13 @@ impl Default for Debug { disable_loop_cover_traffic_stream: false, disable_main_poisson_packet_distribution: false, use_extended_packet_size: None, + minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, + maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, + minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, + retransmission_reply_surb_request_size: DEFAULT_RETRANSMISSION_REPLY_SURB_REQUEST_SIZE, + maximum_reply_surb_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD, } } } diff --git a/clients/native/examples/websocket_binarysend.rs b/clients/native/examples/websocket_binarysend.rs index f103815af2..2c22f0d03b 100644 --- a/clients/native/examples/websocket_binarysend.rs +++ b/clients/native/examples/websocket_binarysend.rs @@ -25,58 +25,59 @@ async fn get_self_address(ws_stream: &mut WebSocketStream recipient, + ServerResponse::SelfAddress(recipient) => *recipient, _ => panic!("received an unexpected response!"), } } async fn send_file_with_reply() { - let uri = "ws://localhost:1977"; - let (mut ws_stream, _) = connect_async(uri).await.unwrap(); - - let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient); - - let read_data = std::fs::read("examples/dummy_file").unwrap(); - - let send_request = ClientRequest::Send { - recipient, - message: read_data, - with_reply_surb: true, - connection_id: Some(0), - }; - - println!("sending content of 'dummy_file' over the mix network..."); - let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await; - - let received = match response { - ServerResponse::Received(received) => received, - _ => panic!("received an unexpected response!"), - }; - - println!("writing the file back to the disk!"); - std::fs::write("examples/received_file_withreply", received.message).unwrap(); - - let reply_message = b"hello from reply SURB! - thanks for sending me the file!".to_vec(); - let reply_request = ClientRequest::Reply { - message: reply_message.clone(), - reply_surb: received.reply_surb.unwrap(), - }; - - println!( - "sending {:?} (using reply SURB!) over the mix network...", - String::from_utf8(reply_message).unwrap() - ); - let response = send_message_and_get_response(&mut ws_stream, reply_request.serialize()).await; - let received = match response { - ServerResponse::Received(received) => received, - _ => panic!("received an unexpected response!"), - }; - - println!( - "received {:#?} from the mix network!", - String::from_utf8(received.message).unwrap() - ); + todo!("reimplement surb usage here : )") + // let uri = "ws://localhost:1977"; + // let (mut ws_stream, _) = connect_async(uri).await.unwrap(); + // + // let recipient = get_self_address(&mut ws_stream).await; + // println!("our full address is: {}", recipient); + // + // let read_data = std::fs::read("examples/dummy_file").unwrap(); + // + // let send_request = ClientRequest::Send { + // recipient, + // message: read_data, + // with_reply_surb: true, + // connection_id: Some(0), + // }; + // + // println!("sending content of 'dummy_file' over the mix network..."); + // let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await; + // + // let received = match response { + // ServerResponse::Received(received) => received, + // _ => panic!("received an unexpected response!"), + // }; + // + // println!("writing the file back to the disk!"); + // std::fs::write("examples/received_file_withreply", received.message).unwrap(); + // + // let reply_message = b"hello from reply SURB! - thanks for sending me the file!".to_vec(); + // let reply_request = ClientRequest::Reply { + // message: reply_message.clone(), + // reply_surb: received.reply_surb.unwrap(), + // }; + // + // println!( + // "sending {:?} (using reply SURB!) over the mix network...", + // String::from_utf8(reply_message).unwrap() + // ); + // let response = send_message_and_get_response(&mut ws_stream, reply_request.serialize()).await; + // let received = match response { + // ServerResponse::Received(received) => received, + // _ => panic!("received an unexpected response!"), + // }; + // + // println!( + // "received {:#?} from the mix network!", + // String::from_utf8(received.message).unwrap() + // ); } async fn send_file_without_reply() { @@ -91,7 +92,6 @@ async fn send_file_without_reply() { let send_request = ClientRequest::Send { recipient, message: read_data, - with_reply_surb: false, connection_id: Some(0), }; diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index f9c04ca1d7..f031a0e816 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::template::config_template; -use client_core::config::Config as BaseConfig; pub use client_core::config::MISSING_VALUE; +use client_core::config::{Config as BaseConfig, Debug}; use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use config::NymConfig; use serde::{Deserialize, Serialize}; @@ -100,6 +100,10 @@ impl Config { &mut self.base } + pub fn get_debug_settings(&self) -> &Debug { + self.get_base().get_debug_config() + } + pub fn get_socket_type(&self) -> SocketType { self.socket.socket_type } diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 4d60e3ad54..52cb475703 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -1,6 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::client::config::{Config, SocketType}; +use crate::error::ClientError; +use crate::websocket; use client_connections::{ ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths, TransmissionLane, }; @@ -16,7 +19,11 @@ use client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController, ReconstructedMessagesReceiver, }; -use client_core::client::reply_key_storage::ReplyKeyStorage; +use client_core::client::replies::reply_controller; +use client_core::client::replies::reply_controller::{ + ReplyControllerReceiver, ReplyControllerSender, +}; +use client_core::client::replies::reply_storage::{CombinedReplyStorage, SentReplyKeys}; use client_core::client::topology_control::{ TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, }; @@ -32,15 +39,10 @@ use gateway_client::{ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::receiver::ReconstructedMessage; -use tap::TapFallible; use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; -use crate::client::config::{Config, SocketType}; -use crate::error::ClientError; -use crate::websocket; - pub(crate) mod config; pub struct NymClient { @@ -118,24 +120,19 @@ impl NymClient { fn start_real_traffic_controller( &self, topology_accessor: TopologyAccessor, - reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, mix_sender: BatchMixMessageSender, + reply_storage: CombinedReplyStorage, + reply_controller_sender: ReplyControllerSender, + reply_controller_receiver: ReplyControllerReceiver, lane_queue_lengths: LaneQueueLengths, client_connection_rx: ConnectionCommandReceiver, shutdown: ShutdownListener, ) { let mut controller_config = real_messages_control::Config::new( + self.config.get_debug_settings(), self.key_manager.ack_key(), - self.config.get_base().get_ack_wait_multiplier(), - self.config.get_base().get_ack_wait_addition(), - self.config.get_base().get_average_ack_delay(), - self.config.get_base().get_message_sending_average_delay(), - self.config.get_base().get_average_packet_delay(), - self.config - .get_base() - .get_disabled_main_poisson_packet_distribution(), self.as_mix_recipient(), ); @@ -152,7 +149,9 @@ impl NymClient { input_receiver, mix_sender, topology_accessor, - reply_key_storage, + reply_storage, + reply_controller_sender, + reply_controller_receiver, lane_queue_lengths, client_connection_rx, ) @@ -165,7 +164,8 @@ impl NymClient { &self, query_receiver: ReceivedBufferRequestReceiver, mixnet_receiver: MixnetMessageReceiver, - reply_key_storage: ReplyKeyStorage, + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, shutdown: ShutdownListener, ) { info!("Starting received messages buffer controller..."); @@ -174,6 +174,7 @@ impl NymClient { query_receiver, mixnet_receiver, reply_key_storage, + reply_controller_sender, ) .start_with_shutdown(shutdown) } @@ -318,14 +319,29 @@ impl NymClient { /// EXPERIMENTAL DIRECT RUST API /// It's untested and there are absolutely no guarantees about it (but seems to have worked /// well enough in local tests) - pub async fn send_message( + pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec) { + let lane = TransmissionLane::General; + let input_msg = InputMessage::new_regular(recipient, message, lane); + + self.input_tx + .as_ref() + .expect("start method was not called before!") + .send(input_msg) + .await + .expect("InputMessageReceiver has stopped receiving!"); + } + + /// EXPERIMENTAL DIRECT RUST API + /// It's untested and there are absolutely no guarantees about it (but seems to have worked + /// well enough in local tests) + pub async fn send_anonymous_message( &mut self, recipient: Recipient, message: Vec, - with_reply_surb: bool, + reply_surbs: u32, ) { let lane = TransmissionLane::General; - let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane); + let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane); self.input_tx .as_ref() @@ -338,8 +354,9 @@ impl NymClient { /// EXPERIMENTAL DIRECT RUST API /// It's untested and there are absolutely no guarantees about it (but seems to have worked /// well enough in local tests) - pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec) { - let input_msg = InputMessage::new_reply(reply_surb, message); + pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec) { + let lane = TransmissionLane::General; + let input_msg = InputMessage::new_reply(recipient_tag, message, lane); self.input_tx .as_ref() @@ -409,16 +426,29 @@ impl NymClient { let (ack_sender, ack_receiver) = mpsc::unbounded(); let shared_topology_accessor = TopologyAccessor::new(); - let reply_key_storage = - ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) - .tap_err(|err| { - log::error!("Failed to load reply key storage - is it perhaps already in use?"); - log::error!("{}", err); - })?; + // channels responsible for dealing with reply-related fun + let (reply_controller_sender, reply_controller_receiver) = + reply_controller::new_control_channels(); // Shutdown notifier for signalling tasks to stop let shutdown = ShutdownNotifier::default(); + // ===================== + // ===================== + // ======TEMPORARY====== + // ===================== + // ===================== + // TODO: lower the value and improve the reliability when it's low (because it should still work in that case) + // (it goes insane at 10,200) + let reply_storage = CombinedReplyStorage::new( + self.config + .get_base() + .get_minimum_reply_surb_storage_threshold(), + self.config + .get_base() + .get_maximum_reply_surb_storage_threshold(), + ); + // the components are started in very specific order. Unless you know what you are doing, // do not change that. self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) @@ -426,7 +456,8 @@ impl NymClient { self.start_received_messages_buffer_controller( received_buffer_request_receiver, mixnet_messages_receiver, - reply_key_storage.clone(), + reply_storage.key_storage(), + reply_controller_sender.clone(), shutdown.subscribe(), ); @@ -451,10 +482,12 @@ impl NymClient { self.start_real_traffic_controller( shared_topology_accessor.clone(), - reply_key_storage, ack_receiver, input_receiver, sphinx_message_sender.clone(), + reply_storage, + reply_controller_sender, + reply_controller_receiver, shared_lane_queue_lengths.clone(), client_connection_rx, shutdown.subscribe(), diff --git a/clients/native/src/error.rs b/clients/native/src/error.rs index 6ffdc86e99..02f2bf9631 100644 --- a/clients/native/src/error.rs +++ b/clients/native/src/error.rs @@ -1,4 +1,4 @@ -use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError}; +use client_core::error::ClientCoreError; use crypto::asymmetric::identity::Ed25519RecoveryError; use gateway_client::error::GatewayClientError; use validator_client::ValidatorClientError; @@ -15,9 +15,6 @@ pub enum ClientError { ValidatorClientError(#[from] ValidatorClientError), #[error("client-core error: {0}")] ClientCoreError(#[from] ClientCoreError), - #[error("Reply key storage error: {0}")] - ReplyKeyStorageError(#[from] ReplyKeyStorageError), - #[error("Failed to load config for: {0}")] FailedToLoadConfig(String), #[error("Failed local version check, client and config mismatch")] diff --git a/clients/native/src/websocket/handler.rs b/clients/native/src/websocket/handler.rs index 11d0dda55f..68fa9aba79 100644 --- a/clients/native/src/websocket/handler.rs +++ b/clients/native/src/websocket/handler.rs @@ -14,7 +14,7 @@ use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; use log::*; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::receiver::ReconstructedMessage; use tokio::net::TcpStream; use tokio_tungstenite::{ @@ -89,18 +89,22 @@ impl Handler { async fn handle_send( &mut self, - recipient: &Recipient, + recipient: Recipient, message: Vec, - with_reply_surb: bool, connection_id: Option, ) -> Option { + info!( + "Attempting to send {:.2} kiB message to {recipient} on connection_id {connection_id:?}", + message.len() as f64 / 1024.0 + ); + // We map the absence of a connection id as going into the general lane. let lane = connection_id.map_or(TransmissionLane::General, |id| { TransmissionLane::ConnectionId(id) }); // the ack control is now responsible for chunking, etc. - let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane); + let input_msg = InputMessage::new_regular(recipient, message, lane); self.msg_input .send(input_msg) .await @@ -109,53 +113,119 @@ impl Handler { // Only reply back with a `LaneQueueLength` if the sender providided a connection id let connection_id = match lane { TransmissionLane::General - | TransmissionLane::Reply + | TransmissionLane::ReplySurbRequest | TransmissionLane::Retransmission - | TransmissionLane::Control => return None, + | TransmissionLane::AdditionalReplySurbs => return None, TransmissionLane::ConnectionId(id) => id, }; // on receiving a send, we reply back the current lane queue length for that connection id. // Note that this does _NOT_ take into account the packets that have been received but not // yet reach `OutQueueControl`, so it might be a tad low. - let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else { - log::warn!( - "Failed to get the lane queue length lock, \ - not responding back with the current queue length" - ); - return None; + if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { + let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); + return Some(ServerResponse::LaneQueueLength { + lane: connection_id, + queue_length, + }); + } + + log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); + None + } + + async fn handle_send_anonymous( + &mut self, + recipient: Recipient, + message: Vec, + reply_surbs: u32, + connection_id: Option, + ) -> Option { + info!( + "Attempting to anonymously send {:.2} kiB message to {recipient} on connection_id {connection_id:?} while attaching {reply_surbs} replySURBs.", + message.len() as f64 / 1024.0 + ); + + // We map the absence of a connection id as going into the general lane. + let lane = connection_id.map_or(TransmissionLane::General, |id| { + TransmissionLane::ConnectionId(id) + }); + + let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane); + self.msg_input + .send(input_msg) + .await + .expect("InputMessageReceiver has stopped receiving!"); + + // Only reply back with a `LaneQueueLength` if the sender providided a connection id + let connection_id = match lane { + TransmissionLane::General + | TransmissionLane::ReplySurbRequest + | TransmissionLane::Retransmission + | TransmissionLane::AdditionalReplySurbs => return None, + TransmissionLane::ConnectionId(id) => id, }; - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - Some(ServerResponse::LaneQueueLength(connection_id, queue_length)) + // on receiving a send, we reply back the current lane queue length for that connection id. + // Note that this does _NOT_ take into account the packets that have been received but not + // yet reach `OutQueueControl`, so it might be a tad low. + if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { + let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); + return Some(ServerResponse::LaneQueueLength { + lane: connection_id, + queue_length, + }); + } + + log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); + None } async fn handle_reply( &mut self, - reply_surb: ReplySurb, + recipient_tag: AnonymousSenderTag, message: Vec, + connection_id: Option, ) -> Option { - if message.len() > ReplySurb::max_msg_len(Default::default()) { - return Some( - ServerResponse::new_error( - format!( - "too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", - message.len(), ReplySurb::max_msg_len(Default::default())) - ) - ); - } + info!("Attempting to send {:.2} kiB reply message to {recipient_tag:?} on connection_id {connection_id:?}", message.len() as f64 / 1024.0); + + // We map the absence of a connection id as going into the general lane. + let lane = connection_id.map_or(TransmissionLane::General, |id| { + TransmissionLane::ConnectionId(id) + }); - let input_msg = InputMessage::new_reply(reply_surb, message); + let input_msg = InputMessage::new_reply(recipient_tag, message, lane); self.msg_input .send(input_msg) .await .expect("InputMessageReceiver has stopped receiving!"); + // Only reply back with a `LaneQueueLength` if the sender providided a connection id + let connection_id = match lane { + TransmissionLane::General + | TransmissionLane::ReplySurbRequest + | TransmissionLane::Retransmission + | TransmissionLane::AdditionalReplySurbs => return None, + TransmissionLane::ConnectionId(id) => id, + }; + + // on receiving a send, we reply back the current lane queue length for that connection id. + // Note that this does _NOT_ take into account the packets that have been received but not + // yet reach `OutQueueControl`, so it might be a tad low. + if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { + let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); + return Some(ServerResponse::LaneQueueLength { + lane: connection_id, + queue_length, + }); + } + + log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); None } fn handle_self_address(&self) -> ServerResponse { - ServerResponse::SelfAddress(self.self_full_address) + ServerResponse::SelfAddress(Box::new(self.self_full_address)) } fn handle_closed_connection(&self, connection_id: u64) -> Option { @@ -175,7 +245,10 @@ impl Handler { let lane = TransmissionLane::ConnectionId(connection_id); let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - Some(ServerResponse::LaneQueueLength(connection_id, queue_length)) + Some(ServerResponse::LaneQueueLength { + lane: connection_id, + queue_length, + }) } async fn handle_request(&mut self, request: ClientRequest) -> Option { @@ -183,16 +256,25 @@ impl Handler { ClientRequest::Send { recipient, message, - with_reply_surb, + connection_id, + } => self.handle_send(recipient, message, connection_id).await, + + ClientRequest::SendAnonymous { + recipient, + message, + reply_surbs, connection_id, } => { - self.handle_send(&recipient, message, with_reply_surb, connection_id) + self.handle_send_anonymous(recipient, message, reply_surbs, connection_id) .await } + ClientRequest::Reply { message, - reply_surb, - } => self.handle_reply(reply_surb, message).await, + sender_tag, + connection_id, + } => self.handle_reply(sender_tag, message, connection_id).await, + ClientRequest::SelfAddress => Some(self.handle_self_address()), ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id), ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id), @@ -299,8 +381,7 @@ impl Handler { if let Some(response) = self.handle_ws_request(socket_msg).await { if let Err(err) = self.send_websocket_response(response).await { warn!( - "Failed to send message over websocket: {}. Assuming the connection is dead.", - err + "Failed to send message over websocket: {err}. Assuming the connection is dead.", ); break; } diff --git a/clients/native/src/websocket/listener.rs b/clients/native/src/websocket/listener.rs index bdaa27d4fc..c7825fb345 100644 --- a/clients/native/src/websocket/listener.rs +++ b/clients/native/src/websocket/listener.rs @@ -54,9 +54,8 @@ impl Listener { Ok((mut socket, remote_addr)) => { debug!("Received connection from {:?}", remote_addr); if self.state.is_connected() { - warn!("tried to duplicate!"); + warn!("Tried to open a duplicate websocket connection. The request came from {}", remote_addr); // if we've already got a connection, don't allow another one - debug!("but there was already a connection present!"); // while we only ever want to accept a single connection, we don't want // to leave clients hanging (and also allow for reconnection if it somehow // was dropped) diff --git a/clients/native/websocket-requests/src/error.rs b/clients/native/websocket-requests/src/error.rs index f9fdd029cf..2d9035c19d 100644 --- a/clients/native/websocket-requests/src/error.rs +++ b/clients/native/websocket-requests/src/error.rs @@ -24,8 +24,11 @@ impl fmt::Debug for Error { } impl Error { - pub fn new(kind: ErrorKind, message: String) -> Self { - Error { kind, message } + pub fn new>(kind: ErrorKind, message: S) -> Self { + Error { + kind, + message: message.into(), + } } } @@ -62,6 +65,31 @@ pub enum ErrorKind { Other = 0xFF, } +impl TryFrom for ErrorKind { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (ErrorKind::EmptyRequest as u8) => Ok(ErrorKind::EmptyRequest), + _ if value == (ErrorKind::TooShortRequest as u8) => Ok(ErrorKind::TooShortRequest), + _ if value == (ErrorKind::UnknownRequest as u8) => Ok(ErrorKind::UnknownRequest), + _ if value == (ErrorKind::MalformedRequest as u8) => Ok(ErrorKind::MalformedRequest), + + _ if value == (ErrorKind::EmptyResponse as u8) => Ok(ErrorKind::EmptyResponse), + _ if value == (ErrorKind::TooShortResponse as u8) => Ok(ErrorKind::TooShortResponse), + _ if value == (ErrorKind::UnknownResponse as u8) => Ok(ErrorKind::UnknownResponse), + _ if value == (ErrorKind::MalformedResponse as u8) => Ok(ErrorKind::MalformedResponse), + + _ if value == (ErrorKind::Other as u8) => Ok(ErrorKind::Other), + + n => Err(Error::new( + ErrorKind::MalformedResponse, + format!("invalid error code {}", n), + )), + } + } +} + impl ErrorKind { pub(crate) fn as_str(&self) -> &'static str { match *self { diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index 315399ae3d..f04f9d1dd3 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 // all variable size data is always prefixed with u64 length @@ -7,69 +7,115 @@ use crate::error::{self, ErrorKind}; use crate::text::ClientRequestText; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; use std::convert::{TryFrom, TryInto}; use std::mem::size_of; -/// Value tag representing [`Send`] variant of the [`ClientRequest`] -pub const SEND_REQUEST_TAG: u8 = 0x00; +#[repr(u8)] +enum ClientRequestTag { + /// Value tag representing [`Send`] variant of the [`ClientRequest`] + Send = 0x00, -/// Value tag representing [`Reply`] variant of the [`ClientRequest`] -pub const REPLY_REQUEST_TAG: u8 = 0x01; + /// Value tag representing [`SendAnonymous`] variant of the [`ClientRequest`] + SendAnonymous = 0x01, -/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`] -pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02; + /// Value tag representing [`Reply`] variant of the [`ClientRequest`] + Reply = 0x02, -/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`] -pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03; + /// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`] + SelfAddress = 0x03, -/// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`] -pub const GET_LANE_QUEUE_LENGHT_TAG: u8 = 0x04; + /// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`] + ClosedConnection = 0x04, + + /// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`] + GetLaneQueueLength = 0x05, +} + +impl TryFrom for ClientRequestTag { + type Error = error::Error; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (Self::Send as u8) => Ok(Self::Send), + _ if value == (Self::SendAnonymous as u8) => Ok(Self::SendAnonymous), + _ if value == (Self::Reply as u8) => Ok(Self::Reply), + _ if value == (Self::SelfAddress as u8) => Ok(Self::SelfAddress), + _ if value == (Self::ClosedConnection as u8) => Ok(Self::ClosedConnection), + _ if value == (Self::GetLaneQueueLength as u8) => Ok(Self::GetLaneQueueLength), + n => Err(error::Error::new( + ErrorKind::UnknownRequest, + format!("{n} does not correspond to any valid request tag"), + )), + } + } +} #[allow(non_snake_case)] #[derive(Debug)] pub enum ClientRequest { + /// The simplest message variant where no additional information is attached. + /// You're simply sending your `data` to specified `recipient` without any tagging. + /// + /// Ends up with `NymMessage::Plain` variant Send { recipient: Recipient, message: Vec, - // Perhaps we could change it to a number to indicate how many reply_SURBs we want to include? - with_reply_surb: bool, connection_id: Option, }, + + /// Create a message used for a duplex anonymous communication where the recipient + /// will never learn of our true identity. This is achieved by carefully sending `reply_surbs`. + /// + /// Note that if reply_surbs is set to zero then + /// this variant requires the client having sent some reply_surbs in the past + /// (and thus the recipient also knowing our sender tag). + /// + /// Ends up with `NymMessage::Repliable` variant + SendAnonymous { + recipient: Recipient, + message: Vec, + reply_surbs: u32, + connection_id: Option, + }, + + /// Attempt to use our internally received and stored `ReplySurb` to send the message back + /// to specified recipient whilst not knowing its full identity (or even gateway). + /// + /// Ends up with `NymMessage::Reply` variant Reply { + sender_tag: AnonymousSenderTag, message: Vec, - reply_surb: ReplySurb, + connection_id: Option, }, + SelfAddress, + ClosedConnection(u64), + GetLaneQueueLength(u64), } // we could have been parsing it directly TryFrom, but we want to retain // information about whether it came from binary or text to send appropriate response back impl ClientRequest { - // SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data - fn serialize_send( - recipient: Recipient, - data: Vec, - with_reply_surb: bool, - connection_id: Option, - ) -> Vec { + // SEND_REQUEST_TAG || recipient || conn_id || data_len || data + fn serialize_send(recipient: Recipient, data: Vec, connection_id: Option) -> Vec { let data_len_bytes = (data.len() as u64).to_be_bytes(); let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); - std::iter::once(SEND_REQUEST_TAG) - .chain(std::iter::once(with_reply_surb as u8)) - .chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant - .chain(conn_id_bytes.iter().cloned()) - .chain(data_len_bytes.iter().cloned()) + + std::iter::once(ClientRequestTag::Send as u8) + .chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant + .chain(conn_id_bytes.into_iter()) + .chain(data_len_bytes.into_iter()) .chain(data.into_iter()) .collect() } - // SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data + // SEND_REQUEST_TAG || recipient || conn_id || data_len || data fn deserialize_send(b: &[u8]) -> Result { - // we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof bytes - if b.len() < 2 + Recipient::LEN + 2 * size_of::() { + // we need to have at least 1 (tag) + Recipient::LEN + 2*sizeof bytes + if b.len() < 1 + Recipient::LEN + 2 * size_of::() { return Err(error::Error::new( ErrorKind::TooShortRequest, "not enough data provided to recover 'send'".to_string(), @@ -77,21 +123,88 @@ impl ClientRequest { } // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], SEND_REQUEST_TAG); + debug_assert_eq!(b[0], ClientRequestTag::Send as u8); - let with_reply_surb = match b[1] { - 0 => false, - 1 => true, - n => { + let mut recipient_bytes = [0u8; Recipient::LEN]; + recipient_bytes.copy_from_slice(&b[1..1 + Recipient::LEN]); + let recipient = match Recipient::try_from_bytes(recipient_bytes) { + Ok(recipient) => recipient, + Err(err) => { return Err(error::Error::new( ErrorKind::MalformedRequest, - format!("invalid reply surb flag {}", n), + format!("malformed recipient: {:?}", err), )) } }; + let mut connection_id_bytes = [0u8; size_of::()]; + connection_id_bytes + .copy_from_slice(&b[1 + Recipient::LEN..1 + Recipient::LEN + size_of::()]); + let connection_id = u64::from_be_bytes(connection_id_bytes); + let connection_id = if connection_id == 0 { + None + } else { + Some(connection_id) + }; + + let data_len_bytes = + &b[1 + Recipient::LEN + size_of::()..1 + Recipient::LEN + 2 * size_of::()]; + let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap()); + let data = &b[1 + Recipient::LEN + 2 * size_of::()..]; + if data.len() as u64 != data_len { + return Err(error::Error::new( + ErrorKind::MalformedRequest, + format!( + "data len has inconsistent length. specified: {} got: {}", + data_len, + data.len() + ), + )); + } + + Ok(ClientRequest::Send { + recipient, + message: data.to_vec(), + connection_id, + }) + } + + // SEND_ANONYMOUS_REQUEST_TAG || reply_surbs || recipient || conn_id || data_len || data + fn serialize_send_anonymous( + recipient: Recipient, + data: Vec, + reply_surbs: u32, + connection_id: Option, + ) -> Vec { + let data_len_bytes = (data.len() as u64).to_be_bytes(); + let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); + + std::iter::once(ClientRequestTag::SendAnonymous as u8) + .chain(reply_surbs.to_be_bytes().into_iter()) + .chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant + .chain(conn_id_bytes.into_iter()) + .chain(data_len_bytes.into_iter()) + .chain(data.into_iter()) + .collect() + } + + // SEND_ANONYMOUS_REQUEST_TAG || reply_surbs || recipient || data_len || data + fn deserialize_send_anonymous(b: &[u8]) -> Result { + // we need to have at least 1 (tag) + sizeof (num surbs) + Recipient::LEN + 2 *sizeof bytes + if b.len() < 1 + size_of::() + Recipient::LEN + 2 * size_of::() { + return Err(error::Error::new( + ErrorKind::TooShortRequest, + "not enough data provided to recover 'send_anonymous'".to_string(), + )); + } + + // this MUST match because it was called by 'deserialize' + debug_assert_eq!(b[0], ClientRequestTag::SendAnonymous as u8); + + let reply_surbs = u32::from_be_bytes([b[1], b[2], b[3], b[4]]); + let mut recipient_bytes = [0u8; Recipient::LEN]; - recipient_bytes.copy_from_slice(&b[2..2 + Recipient::LEN]); + recipient_bytes.copy_from_slice(&b[5..5 + Recipient::LEN]); let recipient = match Recipient::try_from_bytes(recipient_bytes) { Ok(recipient) => recipient, Err(err) => { @@ -104,7 +217,7 @@ impl ClientRequest { let mut connection_id_bytes = [0u8; size_of::()]; connection_id_bytes - .copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::()]); + .copy_from_slice(&b[5 + Recipient::LEN..5 + Recipient::LEN + size_of::()]); let connection_id = u64::from_be_bytes(connection_id_bytes); let connection_id = if connection_id == 0 { None @@ -113,9 +226,9 @@ impl ClientRequest { }; let data_len_bytes = - &b[2 + Recipient::LEN + size_of::()..2 + Recipient::LEN + 2 * size_of::()]; + &b[5 + Recipient::LEN + size_of::()..5 + Recipient::LEN + 2 * size_of::()]; let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap()); - let data = &b[2 + Recipient::LEN + 2 * size_of::()..]; + let data = &b[5 + Recipient::LEN + 2 * size_of::()..]; if data.len() as u64 != data_len { return Err(error::Error::new( ErrorKind::MalformedRequest, @@ -127,34 +240,34 @@ impl ClientRequest { )); } - Ok(ClientRequest::Send { - with_reply_surb, + Ok(ClientRequest::SendAnonymous { + reply_surbs, recipient, message: data.to_vec(), connection_id, }) } - // REPLY_REQUEST_TAG || surb_len || surb || message_len || message - fn serialize_reply(message: Vec, reply_surb: &ReplySurb) -> Vec { - let reply_surb_bytes = reply_surb.to_bytes(); - let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes(); + // REPLY_REQUEST_TAG || SENDER_TAG || conn_id || message_len || message + fn serialize_reply( + message: Vec, + sender_tag: AnonymousSenderTag, + connection_id: Option, + ) -> Vec { let message_len_bytes = (message.len() as u64).to_be_bytes(); + let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); - std::iter::once(REPLY_REQUEST_TAG) - .chain(surb_len_bytes.iter().cloned()) - .chain(reply_surb_bytes.into_iter()) - .chain(message_len_bytes.iter().cloned()) + std::iter::once(ClientRequestTag::Reply as u8) + .chain(sender_tag.into_iter()) + .chain(conn_id_bytes.into_iter()) + .chain(message_len_bytes.into_iter()) .chain(message.into_iter()) .collect() } - // REPLY_REQUEST_TAG || surb_len || surb || message_len || message + // REPLY_REQUEST_TAG || SENDER_TAG || conn_id || message_len || message fn deserialize_reply(b: &[u8]) -> Result { - // we need to have at the very least 2 * sizeof bytes (in case, for some peculiar reason - // message and reply surb were 0 len - the request would still be malformed, but would in theory - // be parse'able) - if b.len() < 1 + 2 * size_of::() { + if b.len() < 1 + SENDER_TAG_SIZE + 2 * size_of::() { return Err(error::Error::new( ErrorKind::TooShortRequest, "not enough data provided to recover 'reply'".to_string(), @@ -162,42 +275,27 @@ impl ClientRequest { } // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], REPLY_REQUEST_TAG); - - let reply_surb_len = - u64::from_be_bytes(b[1..1 + size_of::()].as_ref().try_into().unwrap()); - - // make sure we won't go out of bounds here - if reply_surb_len > (b.len() - 1 + 2 * size_of::()) as u64 { - return Err(error::Error::new( - ErrorKind::MalformedRequest, - format!( - "not enough data to recover reply surb with specified length {}", - reply_surb_len - ), - )); - } + debug_assert_eq!(b[0], ClientRequestTag::Reply as u8); - let surb_bound = 1 + size_of::() + reply_surb_len as usize; + // the unwrap here is fine as we're definitely using exactly SENDER_TAG_SIZE bytes + let sender_tag = b[1..1 + SENDER_TAG_SIZE].try_into().unwrap(); - let reply_surb_bytes = &b[1 + size_of::()..surb_bound]; - let reply_surb = match ReplySurb::from_bytes(reply_surb_bytes) { - Ok(reply_surb) => reply_surb, - Err(err) => { - return Err(error::Error::new( - ErrorKind::MalformedRequest, - format!("malformed reply surb: {:?}", err), - )) - } + let mut connection_id_bytes = [0u8; size_of::()]; + connection_id_bytes + .copy_from_slice(&b[1 + SENDER_TAG_SIZE..1 + SENDER_TAG_SIZE + size_of::()]); + let connection_id = u64::from_be_bytes(connection_id_bytes); + let connection_id = if connection_id == 0 { + None + } else { + Some(connection_id) }; let message_len = u64::from_be_bytes( - b[surb_bound..surb_bound + size_of::()] - .as_ref() + b[1 + SENDER_TAG_SIZE + size_of::()..1 + SENDER_TAG_SIZE + 2 * size_of::()] .try_into() .unwrap(), ); - let message = &b[surb_bound + size_of::()..]; + let message = &b[1 + SENDER_TAG_SIZE + 2 * size_of::()..]; if message.len() as u64 != message_len { return Err(error::Error::new( ErrorKind::MalformedRequest, @@ -208,33 +306,32 @@ impl ClientRequest { ), )); } - // TODO: should this blow HERE, i.e. during deserialization that the data you're trying - // to send via reply is too long? Ok(ClientRequest::Reply { - reply_surb, message: message.to_vec(), + sender_tag, + connection_id, }) } // SELF_ADDRESS_REQUEST_TAG fn serialize_self_address() -> Vec { - std::iter::once(SELF_ADDRESS_REQUEST_TAG).collect() + vec![ClientRequestTag::SelfAddress as u8] } // SELF_ADDRESS_REQUEST_TAG - fn deserialize_self_address(b: &[u8]) -> Self { + fn deserialize_self_address(b: &[u8]) -> Result { // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], SELF_ADDRESS_REQUEST_TAG); + debug_assert_eq!(b[0], ClientRequestTag::SelfAddress as u8); - ClientRequest::SelfAddress + Ok(ClientRequest::SelfAddress) } // CLOSED_CONNECTION_REQUEST_TAG fn serialize_closed_connection(connection_id: u64) -> Vec { let conn_id_bytes = connection_id.to_be_bytes(); - std::iter::once(CLOSED_CONNECTION_REQUEST_TAG) - .chain(conn_id_bytes.iter().copied()) + std::iter::once(ClientRequestTag::ClosedConnection as u8) + .chain(conn_id_bytes.into_iter()) .collect() } @@ -243,12 +340,12 @@ impl ClientRequest { if b.len() != 1 + size_of::() { return Err(error::Error::new( ErrorKind::MalformedRequest, - "the received closed connection has invalid length".to_string(), + "The received closed connection has invalid length", )); } // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG); + debug_assert_eq!(b[0], ClientRequestTag::ClosedConnection as u8); let mut connection_id_bytes = [0u8; size_of::()]; connection_id_bytes.copy_from_slice(&b[1..=size_of::()]); @@ -260,8 +357,8 @@ impl ClientRequest { // GET_LANE_QUEUE_LENGHT_TAG fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec { let conn_id_bytes = connection_id.to_be_bytes(); - std::iter::once(GET_LANE_QUEUE_LENGHT_TAG) - .chain(conn_id_bytes.iter().copied()) + std::iter::once(ClientRequestTag::GetLaneQueueLength as u8) + .chain(conn_id_bytes.into_iter()) .collect() } @@ -270,12 +367,12 @@ impl ClientRequest { if b.len() != 1 + size_of::() { return Err(error::Error::new( ErrorKind::MalformedRequest, - "the received get lane queue length has invalid length".to_string(), + "The received get lane queue lengths has invalid length", )); } // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], GET_LANE_QUEUE_LENGHT_TAG); + debug_assert_eq!(b[0], ClientRequestTag::GetLaneQueueLength as u8); let mut connection_id_bytes = [0u8; size_of::()]; connection_id_bytes.copy_from_slice(&b[1..=size_of::()]); @@ -289,14 +386,21 @@ impl ClientRequest { ClientRequest::Send { recipient, message, - with_reply_surb, connection_id, - } => Self::serialize_send(recipient, message, with_reply_surb, connection_id), + } => Self::serialize_send(recipient, message, connection_id), + + ClientRequest::SendAnonymous { + recipient, + message, + reply_surbs, + connection_id, + } => Self::serialize_send_anonymous(recipient, message, reply_surbs, connection_id), ClientRequest::Reply { message, - reply_surb, - } => Self::serialize_reply(message, &reply_surb), + sender_tag, + connection_id, + } => Self::serialize_reply(message, sender_tag, connection_id), ClientRequest::SelfAddress => Self::serialize_self_address(), @@ -316,28 +420,16 @@ impl ClientRequest { )); } - if b.len() < size_of::() { - return Err(error::Error::new( - ErrorKind::TooShortRequest, - format!( - "not enough data provided to recover request tag. Provided only {} bytes", - b.len() - ), - )); - } - let request_tag = b[0]; + let request_tag = ClientRequestTag::try_from(b[0])?; // determine what kind of request that is and try to deserialize it match request_tag { - SEND_REQUEST_TAG => Self::deserialize_send(b), - REPLY_REQUEST_TAG => Self::deserialize_reply(b), - SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)), - CLOSED_CONNECTION_REQUEST_TAG => Self::deserialize_closed_connection(b), - GET_LANE_QUEUE_LENGHT_TAG => Self::deserialize_get_lane_queue_length(b), - n => Err(error::Error::new( - ErrorKind::UnknownRequest, - format!("type {n}"), - )), + ClientRequestTag::Send => Self::deserialize_send(b), + ClientRequestTag::SendAnonymous => Self::deserialize_send_anonymous(b), + ClientRequestTag::Reply => Self::deserialize_reply(b), + ClientRequestTag::SelfAddress => Self::deserialize_self_address(b), + ClientRequestTag::ClosedConnection => Self::deserialize_closed_connection(b), + ClientRequestTag::GetLaneQueueLength => Self::deserialize_get_lane_queue_length(b), } } @@ -365,50 +457,52 @@ mod tests { let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); let recipient_string = recipient.to_string(); - let send_request_no_surb = ClientRequest::Send { + let send_request = ClientRequest::Send { recipient, message: b"foomp".to_vec(), - with_reply_surb: false, connection_id: Some(42), }; - let bytes = send_request_no_surb.serialize(); + let bytes = send_request.serialize(); let recovered = ClientRequest::deserialize(&bytes).unwrap(); match recovered { ClientRequest::Send { recipient, message, - with_reply_surb, connection_id, } => { assert_eq!(recipient.to_string(), recipient_string); assert_eq!(message, b"foomp".to_vec()); - assert!(!with_reply_surb); assert_eq!(connection_id, Some(42)) } _ => unreachable!(), } + } - let send_request_surb = ClientRequest::Send { - recipient, + #[test] + fn send_anonymous_request_serialization_works() { + let original_recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); + + let send_anonymous_request = ClientRequest::SendAnonymous { + recipient: original_recipient, message: b"foomp".to_vec(), - with_reply_surb: true, - connection_id: None, + reply_surbs: 666, + connection_id: Some(42), }; - let bytes = send_request_surb.serialize(); + let bytes = send_anonymous_request.serialize(); let recovered = ClientRequest::deserialize(&bytes).unwrap(); match recovered { - ClientRequest::Send { + ClientRequest::SendAnonymous { recipient, message, - with_reply_surb, + reply_surbs, connection_id, } => { - assert_eq!(recipient.to_string(), recipient_string); + assert_eq!(recipient, original_recipient); assert_eq!(message, b"foomp".to_vec()); - assert!(with_reply_surb); - assert_eq!(connection_id, None) + assert_eq!(connection_id, Some(42)); + assert_eq!(reply_surbs, 666) } _ => unreachable!(), } @@ -416,22 +510,23 @@ mod tests { #[test] fn reply_request_serialization_works() { - let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X"; - let reply_surb = ReplySurb::from_base58_string(reply_surb_string).unwrap(); let reply_request = ClientRequest::Reply { + sender_tag: [8u8; SENDER_TAG_SIZE], message: b"foomp".to_vec(), - reply_surb, + connection_id: Some(42), }; let bytes = reply_request.serialize(); let recovered = ClientRequest::deserialize(&bytes).unwrap(); match recovered { ClientRequest::Reply { - reply_surb, + sender_tag, message, + connection_id, } => { - assert_eq!(reply_surb.to_base58_string(), reply_surb_string); + assert_eq!(sender_tag, [8u8; SENDER_TAG_SIZE]); assert_eq!(message, b"foomp".to_vec()); + assert_eq!(connection_id, Some(42)); } _ => unreachable!(), } @@ -458,4 +553,15 @@ mod tests { _ => unreachable!(), } } + + #[test] + fn get_lane_queue_length_request_serialization_works() { + let close_connection_request = ClientRequest::GetLaneQueueLength(42); + let bytes = close_connection_request.serialize(); + let recovered = ClientRequest::deserialize(&bytes).unwrap(); + match recovered { + ClientRequest::GetLaneQueueLength(id) => assert_eq!(id, 42), + _ => unreachable!(), + } + } } diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index 7a32645957..47d1f21ca1 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -1,36 +1,54 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 // all variable size data is always prefixed with u64 length // tags are u8 -#![allow(unknown_lints)] // due to using `clippy::branches_sharing_code` which does not exist on `stable` just yet - use crate::error::{self, ErrorKind}; use crate::text::ServerResponseText; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::SENDER_TAG_SIZE; use nymsphinx::receiver::ReconstructedMessage; use std::convert::TryInto; use std::mem::size_of; -/// Value tag representing [`Error`] variant of the [`ServerResponse`] -pub const ERROR_RESPONSE_TAG: u8 = 0x00; +#[repr(u8)] +enum ServerResponseTag { + /// Value tag representing [`Error`] variant of the [`ServerResponse`] + Error = 0x00, + + /// Value tag representing [`Received`] variant of the [`ServerResponse`] + Received = 0x01, -/// Value tag representing [`Received`] variant of the [`ServerResponse`] -pub const RECEIVED_RESPONSE_TAG: u8 = 0x01; + /// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`] + SelfAddress = 0x02, -/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`] -pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02; + /// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`] + LaneQueueLength = 0x03, +} -/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`] -pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03; +impl TryFrom for ServerResponseTag { + type Error = error::Error; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (Self::Error as u8) => Ok(Self::Error), + _ if value == (Self::Received as u8) => Ok(Self::Received), + _ if value == (Self::SelfAddress as u8) => Ok(Self::SelfAddress), + _ if value == (Self::LaneQueueLength as u8) => Ok(Self::LaneQueueLength), + n => Err(error::Error::new( + ErrorKind::UnknownResponse, + format!("{n} does not correspond to any valid response tag"), + )), + } + } +} #[derive(Debug)] pub enum ServerResponse { Received(ReconstructedMessage), - SelfAddress(Recipient), - LaneQueueLength(u64, usize), + SelfAddress(Box), + LaneQueueLength { lane: u64, queue_length: usize }, Error(error::Error), } @@ -42,24 +60,19 @@ impl ServerResponse { }) } - // RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg + // RECEIVED_RESPONSE_TAG || 1 | 0 indicating sender_tag || Option || msg_len || msg fn serialize_received(reconstructed_message: ReconstructedMessage) -> Vec { let message_len_bytes = (reconstructed_message.message.len() as u64).to_be_bytes(); - if let Some(reply_surb) = reconstructed_message.reply_surb { - let reply_surb_bytes = reply_surb.to_bytes(); - let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes(); - // with_reply || surb_len || surb || msg_len || msg - std::iter::once(RECEIVED_RESPONSE_TAG) + if let Some(sender_tag) = reconstructed_message.sender_tag { + std::iter::once(ServerResponseTag::Received as u8) .chain(std::iter::once(true as u8)) - .chain(surb_len_bytes.iter().cloned()) - .chain(reply_surb_bytes.iter().cloned()) + .chain(sender_tag.into_iter()) .chain(message_len_bytes.iter().cloned()) .chain(reconstructed_message.message.into_iter()) .collect() } else { - // without_reply || msg_len || msg - std::iter::once(RECEIVED_RESPONSE_TAG) + std::iter::once(ServerResponseTag::Received as u8) .chain(std::iter::once(false as u8)) .chain(message_len_bytes.iter().cloned()) .chain(reconstructed_message.message.into_iter()) @@ -67,10 +80,9 @@ impl ServerResponse { } } - // RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg + // RECEIVED_RESPONSE_TAG || 1 | 0 indicating sender_tag || Option || msg_len || msg fn deserialize_received(b: &[u8]) -> Result { // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], RECEIVED_RESPONSE_TAG); // we must be able to read at the very least if it has a reply_surb and length of some field if b.len() < 2 + size_of::() { @@ -79,101 +91,68 @@ impl ServerResponse { "not enough data provided to recover 'received'".to_string(), )); } + debug_assert_eq!(b[0], ServerResponseTag::Received as u8); - let with_reply_surb = match b[1] { + let has_sender_tag = match b[1] { 0 => false, 1 => true, n => { return Err(error::Error::new( ErrorKind::MalformedResponse, - format!("invalid reply flag {}", n), + format!("invalid sender tag flag {n}"), )) } }; - // this is a false positive as even though the code is the same, it refers to different things - #[allow(clippy::branches_sharing_code)] - if with_reply_surb { - let reply_surb_len = - u64::from_be_bytes(b[2..2 + size_of::()].as_ref().try_into().unwrap()); - - // make sure we won't go out of bounds here - if reply_surb_len > (b.len() - 2 + 2 * size_of::()) as u64 { + let mut i = 2; + let sender_tag = if has_sender_tag { + if b[2..].len() < SENDER_TAG_SIZE { return Err(error::Error::new( - ErrorKind::MalformedResponse, - "not enough bytes to read reply_surb bytes!".to_string(), + ErrorKind::TooShortResponse, + "not enough data provided to recover 'received'".to_string(), )); } - - let surb_bound = 2 + size_of::() + reply_surb_len as usize; - - let reply_surb_bytes = &b[2 + size_of::()..surb_bound]; - let reply_surb = match ReplySurb::from_bytes(reply_surb_bytes) { - Ok(reply_surb) => reply_surb, - Err(err) => { - return Err(error::Error::new( - ErrorKind::MalformedResponse, - format!("malformed reply SURB: {:?}", err), - )) - } - }; - - let message_len = u64::from_be_bytes( - b[surb_bound..surb_bound + size_of::()] - .as_ref() - .try_into() - .unwrap(), - ); - let message = &b[surb_bound + size_of::()..]; - if message.len() as u64 != message_len { - return Err(error::Error::new( - ErrorKind::MalformedResponse, - format!( - "message len has inconsistent length. specified: {} got: {}", - message_len, - message.len() - ), - )); - } - - Ok(ServerResponse::Received(ReconstructedMessage { - message: message.to_vec(), - reply_surb: Some(reply_surb), - })) + i += SENDER_TAG_SIZE; + Some(b[2..2 + SENDER_TAG_SIZE].try_into().unwrap()) } else { - let message_len = - u64::from_be_bytes(b[2..2 + size_of::()].as_ref().try_into().unwrap()); - let message = &b[2 + size_of::()..]; - if message.len() as u64 != message_len { - return Err(error::Error::new( - ErrorKind::MalformedResponse, - format!( - "message len has inconsistent length. specified: {} got: {}", - message_len, - message.len() - ), - )); - } + None + }; - Ok(ServerResponse::Received(ReconstructedMessage { - message: message.to_vec(), - reply_surb: None, - })) + if b[i..].len() < size_of::() { + return Err(error::Error::new( + ErrorKind::TooShortResponse, + "not enough data provided to recover 'received'".to_string(), + )); } + + let message_len = u64::from_be_bytes(b[i..i + size_of::()].try_into().unwrap()); + let message = &b[i + size_of::()..]; + if message.len() as u64 != message_len { + return Err(error::Error::new( + ErrorKind::MalformedResponse, + format!( + "message len has inconsistent length. specified: {} got: {}", + message_len, + message.len() + ), + )); + } + + Ok(ServerResponse::Received(ReconstructedMessage { + message: message.to_vec(), + sender_tag, + })) } // SELF_ADDRESS_RESPONSE_TAG || self_address fn serialize_self_address(address: Recipient) -> Vec { - std::iter::once(SELF_ADDRESS_RESPONSE_TAG) - .chain(address.to_bytes().iter().cloned()) + std::iter::once(ServerResponseTag::SelfAddress as u8) + .chain(address.to_bytes().into_iter()) .collect() } // SELF_ADDRESS_RESPONSE_TAG || self_address fn deserialize_self_address(b: &[u8]) -> Result { - // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], SELF_ADDRESS_RESPONSE_TAG); - if b.len() != 1 + Recipient::LEN { return Err(error::Error::new( ErrorKind::TooShortResponse, @@ -181,6 +160,9 @@ impl ServerResponse { )); } + // this MUST match because it was called by 'deserialize' + debug_assert_eq!(b[0], ServerResponseTag::SelfAddress as u8); + let mut recipient_bytes = [0u8; Recipient::LEN]; recipient_bytes.copy_from_slice(&b[1..1 + Recipient::LEN]); @@ -194,12 +176,12 @@ impl ServerResponse { } }; - Ok(ServerResponse::SelfAddress(recipient)) + Ok(ServerResponse::SelfAddress(Box::new(recipient))) } // LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec { - std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG) + std::iter::once(ServerResponseTag::LaneQueueLength as u8) .chain(lane.to_be_bytes().iter().cloned()) .chain(queue_length.to_be_bytes().iter().cloned()) .collect() @@ -208,7 +190,7 @@ impl ServerResponse { // LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length fn deserialize_lane_queue_length(b: &[u8]) -> Result { // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG); + debug_assert_eq!(b[0], ServerResponseTag::LaneQueueLength as u8); let mut lane_bytes = [0u8; size_of::()]; lane_bytes.copy_from_slice(&b[1..=size_of::()]); @@ -219,15 +201,15 @@ impl ServerResponse { .copy_from_slice(&b[1 + size_of::()..1 + size_of::() + size_of::()]); let queue_length = usize::from_be_bytes(queue_length_bytes); - Ok(ServerResponse::LaneQueueLength(lane, queue_length)) + Ok(ServerResponse::LaneQueueLength { lane, queue_length }) } // ERROR_RESPONSE_TAG || err_code || msg_len || msg fn serialize_error(error: error::Error) -> Vec { let message_len_bytes = (error.message.len() as u64).to_be_bytes(); - std::iter::once(ERROR_RESPONSE_TAG) + std::iter::once(ServerResponseTag::Error as u8) .chain(std::iter::once(error.kind as u8)) - .chain(message_len_bytes.iter().cloned()) + .chain(message_len_bytes.into_iter()) .chain(error.message.into_bytes().into_iter()) .collect() } @@ -235,7 +217,7 @@ impl ServerResponse { // ERROR_RESPONSE_TAG || err_code || msg_len || msg fn deserialize_error(b: &[u8]) -> Result { // this MUST match because it was called by 'deserialize' - debug_assert_eq!(b[0], ERROR_RESPONSE_TAG); + debug_assert_eq!(b[0], ServerResponseTag::Error as u8); if b.len() < size_of::() + size_of::() { return Err(error::Error::new( @@ -244,26 +226,7 @@ impl ServerResponse { )); } - let error_kind = match b[1] { - _ if b[1] == (ErrorKind::EmptyRequest as u8) => ErrorKind::EmptyRequest, - _ if b[1] == (ErrorKind::TooShortRequest as u8) => ErrorKind::TooShortRequest, - _ if b[1] == (ErrorKind::UnknownRequest as u8) => ErrorKind::UnknownRequest, - _ if b[1] == (ErrorKind::MalformedRequest as u8) => ErrorKind::MalformedRequest, - - _ if b[1] == (ErrorKind::EmptyResponse as u8) => ErrorKind::EmptyResponse, - _ if b[1] == (ErrorKind::TooShortResponse as u8) => ErrorKind::TooShortResponse, - _ if b[1] == (ErrorKind::UnknownResponse as u8) => ErrorKind::UnknownResponse, - _ if b[1] == (ErrorKind::MalformedResponse as u8) => ErrorKind::MalformedResponse, - - _ if b[1] == (ErrorKind::Other as u8) => ErrorKind::Other, - - n => { - return Err(error::Error::new( - ErrorKind::MalformedResponse, - format!("invalid error code {}", n), - )) - } - }; + let error_kind = ErrorKind::try_from(b[1])?; let message_len = u64::from_be_bytes(b[2..2 + size_of::()].as_ref().try_into().unwrap()); @@ -300,8 +263,8 @@ impl ServerResponse { ServerResponse::Received(reconstructed_message) => { Self::serialize_received(reconstructed_message) } - ServerResponse::SelfAddress(address) => Self::serialize_self_address(address), - ServerResponse::LaneQueueLength(lane, queue_length) => { + ServerResponse::SelfAddress(address) => Self::serialize_self_address(*address), + ServerResponse::LaneQueueLength { lane, queue_length } => { Self::serialize_lane_queue_length(lane, queue_length) } ServerResponse::Error(err) => Self::serialize_error(err), @@ -328,18 +291,14 @@ impl ServerResponse { )); } - let response_tag = b[0]; + let response_tag = ServerResponseTag::try_from(b[0])?; // determine what kind of response that is and try to deserialize it match response_tag { - RECEIVED_RESPONSE_TAG => Self::deserialize_received(b), - SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b), - LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b), - ERROR_RESPONSE_TAG => Self::deserialize_error(b), - n => Err(error::Error::new( - ErrorKind::UnknownResponse, - format!("type {}", n), - )), + ServerResponseTag::Received => Self::deserialize_received(b), + ServerResponseTag::SelfAddress => Self::deserialize_self_address(b), + ServerResponseTag::LaneQueueLength => Self::deserialize_lane_queue_length(b), + ServerResponseTag::Error => Self::deserialize_error(b), } } @@ -361,35 +320,30 @@ mod tests { #[test] fn received_response_serialization_works() { - let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X"; - - let received_with_surb = ServerResponse::Received(ReconstructedMessage { + let received_with_sender_tag = ServerResponse::Received(ReconstructedMessage { message: b"foomp".to_vec(), - reply_surb: Some(ReplySurb::from_base58_string(reply_surb_string).unwrap()), + sender_tag: Some([42u8; SENDER_TAG_SIZE]), }); - let bytes = received_with_surb.serialize(); + let bytes = received_with_sender_tag.serialize(); let recovered = ServerResponse::deserialize(&bytes).unwrap(); match recovered { ServerResponse::Received(reconstructed) => { assert_eq!(reconstructed.message, b"foomp".to_vec()); - assert_eq!( - reconstructed.reply_surb.unwrap().to_base58_string(), - reply_surb_string - ) + assert_eq!(reconstructed.sender_tag, Some([42u8; SENDER_TAG_SIZE])) } _ => unreachable!(), } - let received_without_surb = ServerResponse::Received(ReconstructedMessage { + let received_without_sender_tag = ServerResponse::Received(ReconstructedMessage { message: b"foomp".to_vec(), - reply_surb: None, + sender_tag: None, }); - let bytes = received_without_surb.serialize(); + let bytes = received_without_sender_tag.serialize(); let recovered = ServerResponse::deserialize(&bytes).unwrap(); match recovered { ServerResponse::Received(reconstructed) => { assert_eq!(reconstructed.message, b"foomp".to_vec()); - assert!(reconstructed.reply_surb.is_none()) + assert!(reconstructed.sender_tag.is_none()) } _ => unreachable!(), } @@ -400,7 +354,7 @@ mod tests { let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); let recipient_string = recipient.to_string(); - let self_address_response = ServerResponse::SelfAddress(recipient); + let self_address_response = ServerResponse::SelfAddress(Box::new(recipient)); let bytes = self_address_response.serialize(); let recovered = ServerResponse::deserialize(&bytes).unwrap(); match recovered { @@ -413,11 +367,14 @@ mod tests { #[test] fn lane_queue_length_response_serialization_works() { - let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42); + let lane_queue_length_response = ServerResponse::LaneQueueLength { + lane: 13, + queue_length: 42, + }; let bytes = lane_queue_length_response.serialize(); let recovered = ServerResponse::deserialize(&bytes).unwrap(); match recovered { - ServerResponse::LaneQueueLength(lane, queue_length) => { + ServerResponse::LaneQueueLength { lane, queue_length } => { assert_eq!(lane, 13); assert_eq!(queue_length, 42) } diff --git a/clients/native/websocket-requests/src/text.rs b/clients/native/websocket-requests/src/text.rs index 1083ecd9a8..f0c1ca8425 100644 --- a/clients/native/websocket-requests/src/text.rs +++ b/clients/native/websocket-requests/src/text.rs @@ -1,11 +1,11 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::error::ErrorKind; use crate::requests::ClientRequest; use crate::responses::ServerResponse; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::ReplySurb; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -19,15 +19,22 @@ pub(super) enum ClientRequestText { Send { message: String, recipient: String, - with_reply_surb: bool, connection_id: Option, }, - SelfAddress, + #[serde(rename_all = "camelCase")] + SendAnonymous { + recipient: String, + message: String, + reply_surbs: u32, + connection_id: Option, + }, #[serde(rename_all = "camelCase")] Reply { + sender_tag: AnonymousSenderTag, message: String, - reply_surb: String, + connection_id: Option, }, + SelfAddress, } impl TryFrom for ClientRequestText { @@ -46,7 +53,6 @@ impl TryInto for ClientRequestText { ClientRequestText::Send { message, recipient, - with_reply_surb, connection_id, } => { let message_bytes = message.into_bytes(); @@ -57,23 +63,38 @@ impl TryInto for ClientRequestText { Ok(ClientRequest::Send { message: message_bytes, recipient, - with_reply_surb, connection_id, }) } - ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress), - ClientRequestText::Reply { + ClientRequestText::SendAnonymous { + recipient, message, - reply_surb, + reply_surbs, + connection_id, } => { let message_bytes = message.into_bytes(); - let reply_surb = ReplySurb::from_base58_string(reply_surb).map_err(|err| { + let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| { Self::Error::new(ErrorKind::MalformedRequest, err.to_string()) })?; + Ok(ClientRequest::SendAnonymous { + recipient, + message: message_bytes, + reply_surbs, + connection_id, + }) + } + ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress), + ClientRequestText::Reply { + sender_tag, + message, + connection_id, + } => { + let message_bytes = message.into_bytes(); Ok(ClientRequest::Reply { + sender_tag, message: message_bytes, - reply_surb, + connection_id, }) } } @@ -89,7 +110,7 @@ pub(super) enum ServerResponseText { #[serde(rename_all = "camelCase")] Received { message: String, - reply_surb: Option, + sender_tag: Option, }, SelfAddress { address: String, @@ -131,15 +152,13 @@ impl From for ServerResponseText { // TODO: ask DH what is more appropriate, lossy utf8 conversion or returning error and then // pure binary later message: String::from_utf8_lossy(&reconstructed.message).into_owned(), - reply_surb: reconstructed - .reply_surb - .map(|reply_surb| reply_surb.to_base58_string()), + sender_tag: reconstructed.sender_tag, } } ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress { address: recipient.to_string(), }, - ServerResponse::LaneQueueLength(lane, queue_length) => { + ServerResponse::LaneQueueLength { lane, queue_length } => { ServerResponseText::LaneQueueLength { lane, queue_length } } ServerResponse::Error(err) => ServerResponseText::Error { diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs index 4c43984f35..a3e171265d 100644 --- a/clients/socks5/src/client/config/mod.rs +++ b/clients/socks5/src/client/config/mod.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::template::config_template; -use client_core::config::Config as BaseConfig; pub use client_core::config::MISSING_VALUE; +use client_core::config::{Config as BaseConfig, Debug}; use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; use config::NymConfig; use nymsphinx::addressing::clients::Recipient; @@ -12,6 +12,9 @@ use std::path::PathBuf; mod template; +const DEFAULT_CONNECTION_START_SURBS: u32 = 20; +const DEFAULT_PER_REQUEST_SURBS: u32 = 3; + #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -19,6 +22,9 @@ pub struct Config { base: BaseConfig, socks5: Socks5, + + #[serde(default)] + socks5_debug: Socks5Debug, } impl NymConfig for Config { @@ -57,6 +63,7 @@ impl Config { Config { base: BaseConfig::new(id), socks5: Socks5::new(provider_mix_address), + socks5_debug: Socks5Debug::default(), } } @@ -70,7 +77,24 @@ impl Config { self } + pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { + self.socks5.send_anonymously = anonymous_replies; + self + } + // getters + pub fn get_base(&self) -> &BaseConfig { + &self.base + } + + pub fn get_base_mut(&mut self) -> &mut BaseConfig { + &mut self.base + } + + pub fn get_debug_settings(&self) -> &Debug { + self.get_base().get_debug_config() + } + pub fn get_config_file_save_location(&self) -> PathBuf { self.config_directory().join(Self::config_file_name()) } @@ -80,17 +104,21 @@ impl Config { .expect("malformed provider address") } - pub fn get_base(&self) -> &BaseConfig { - &self.base - } - - pub fn get_base_mut(&mut self) -> &mut BaseConfig { - &mut self.base + pub fn get_send_anonymously(&self) -> bool { + self.socks5.send_anonymously } pub fn get_listening_port(&self) -> u16 { self.socks5.listening_port } + + pub fn get_connection_start_surbs(&self) -> u32 { + self.socks5_debug.connection_start_surbs + } + + pub fn get_per_request_surbs(&self) -> u32 { + self.socks5_debug.per_request_surbs + } } #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] @@ -101,6 +129,11 @@ pub struct Socks5 { /// The mix address of the provider to which all requests are going to be sent. provider_mix_address: String, + + /// Flag to indicate whether this client shall send reply surbs with each request + /// and expect to receive any replies using those. + /// Note that it almost doubles bandwidth requirements. + send_anonymously: bool, } impl Socks5 { @@ -108,6 +141,7 @@ impl Socks5 { Socks5 { listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: provider_mix_address.into(), + send_anonymously: false, } } } @@ -117,6 +151,26 @@ impl Default for Socks5 { Socks5 { listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: "".into(), + send_anonymously: false, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5Debug { + /// Number of reply SURBs attached to each `Request::Connect` message. + connection_start_surbs: u32, + + /// Number of reply SURBs attached to each `Request::Send` message. + per_request_surbs: u32, +} + +impl Default for Socks5Debug { + fn default() -> Self { + Socks5Debug { + connection_start_surbs: DEFAULT_CONNECTION_START_SURBS, + per_request_surbs: DEFAULT_PER_REQUEST_SURBS, } } } diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 11d3185f56..96646641f1 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -92,6 +92,10 @@ provider_mix_address = '{{ socks5.provider_mix_address }}' # The port on which the client will be listening for incoming requests listening_port = {{ socks5.listening_port }} +# Flag to indicate whether this client shall send reply surbs with each request +# and expect to receive any replies using those. +# Note that it almost doubles bandwidth requirements. +send_anonymously = {{ socks5.send_anonymously }} ##### logging configuration options ##### @@ -104,6 +108,9 @@ listening_port = {{ socks5.listening_port }} # The following options should not be modified unless you know EXACTLY what you are doing # as if set incorrectly, they may impact your anonymity. +[socks5_debug] + + [debug] average_packet_delay = '{{ debug.average_packet_delay }}' diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index c6b734a749..206fc9cd1d 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -5,6 +5,7 @@ use std::sync::atomic::Ordering; use crate::client::config::Config; use crate::error::Socks5ClientError; +use crate::socks; use crate::socks::{ authentication::{AuthenticationMethods, Authenticator, User}, server::SphinxSocksServer, @@ -16,11 +17,16 @@ use client_core::client::inbound_messages::{ }; use client_core::client::key_manager::KeyManager; use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController}; +use client_core::client::real_messages_control; use client_core::client::real_messages_control::RealMessagesController; use client_core::client::received_buffer::{ ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController, }; -use client_core::client::reply_key_storage::ReplyKeyStorage; +use client_core::client::replies::reply_controller; +use client_core::client::replies::reply_controller::{ + ReplyControllerReceiver, ReplyControllerSender, +}; +use client_core::client::replies::reply_storage::{CombinedReplyStorage, SentReplyKeys}; use client_core::client::topology_control::{ TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, }; @@ -37,7 +43,6 @@ use gateway_client::{ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; -use tap::TapFallible; use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; pub mod config; @@ -116,24 +121,19 @@ impl NymClient { fn start_real_traffic_controller( &self, topology_accessor: TopologyAccessor, - reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, mix_sender: BatchMixMessageSender, + reply_storage: CombinedReplyStorage, + reply_controller_sender: ReplyControllerSender, + reply_controller_receiver: ReplyControllerReceiver, client_connection_rx: ConnectionCommandReceiver, lane_queue_lengths: LaneQueueLengths, shutdown: ShutdownListener, ) { - let mut controller_config = client_core::client::real_messages_control::Config::new( + let mut controller_config = real_messages_control::Config::new( + self.config.get_debug_settings(), self.key_manager.ack_key(), - self.config.get_base().get_ack_wait_multiplier(), - self.config.get_base().get_ack_wait_addition(), - self.config.get_base().get_average_ack_delay(), - self.config.get_base().get_message_sending_average_delay(), - self.config.get_base().get_average_packet_delay(), - self.config - .get_base() - .get_disabled_main_poisson_packet_distribution(), self.as_mix_recipient(), ); @@ -150,7 +150,9 @@ impl NymClient { input_receiver, mix_sender, topology_accessor, - reply_key_storage, + reply_storage, + reply_controller_sender, + reply_controller_receiver, lane_queue_lengths, client_connection_rx, ) @@ -163,7 +165,8 @@ impl NymClient { &self, query_receiver: ReceivedBufferRequestReceiver, mixnet_receiver: MixnetMessageReceiver, - reply_key_storage: ReplyKeyStorage, + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, shutdown: ShutdownListener, ) { info!("Starting received messages buffer controller..."); @@ -172,8 +175,9 @@ impl NymClient { query_receiver, mixnet_receiver, reply_key_storage, + reply_controller_sender, ) - .start_with_shutdown(shutdown); + .start_with_shutdown(shutdown) } async fn start_gateway_client( @@ -312,6 +316,11 @@ impl NymClient { self.config.get_provider_mix_address(), self.as_mix_recipient(), lane_queue_lengths, + socks::client::Config::new( + self.config.get_send_anonymously(), + self.config.get_connection_start_surbs(), + self.config.get_per_request_surbs(), + ), shutdown, ); tokio::spawn(async move { @@ -392,16 +401,32 @@ impl NymClient { let (ack_sender, ack_receiver) = mpsc::unbounded(); let shared_topology_accessor = TopologyAccessor::new(); - let reply_key_storage = - ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) - .tap_err(|err| { - log::error!("Failed to load reply key storage - is it perhaps already in use?"); - log::error!("{}", err); - })?; + // let reply_key_storage = + // ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) + // .expect("Failed to load reply key storage!"); + + // channels responsible for dealing with reply-related fun + let (reply_controller_sender, reply_controller_receiver) = + reply_controller::new_control_channels(); // Shutdown notifier for signalling tasks to stop let shutdown = ShutdownNotifier::default(); + // ===================== + // ===================== + // ======TEMPORARY====== + // ===================== + // ===================== + // TODO: lower the value and improve the reliability when it's low (because it should still work in that case) + let reply_storage = CombinedReplyStorage::new( + self.config + .get_base() + .get_minimum_reply_surb_storage_threshold(), + self.config + .get_base() + .get_maximum_reply_surb_storage_threshold(), + ); + // the components are started in very specific order. Unless you know what you are doing, // do not change that. self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) @@ -409,7 +434,8 @@ impl NymClient { self.start_received_messages_buffer_controller( received_buffer_request_receiver, mixnet_messages_receiver, - reply_key_storage.clone(), + reply_storage.key_storage(), + reply_controller_sender.clone(), shutdown.subscribe(), ); @@ -434,10 +460,12 @@ impl NymClient { self.start_real_traffic_controller( shared_topology_accessor.clone(), - reply_key_storage, ack_receiver, input_receiver, sphinx_message_sender.clone(), + reply_storage, + reply_controller_sender, + reply_controller_receiver, client_connection_rx, shared_lane_queue_lengths.clone(), shutdown.subscribe(), diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index 4ebca0e2e5..99dd9a7a42 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -1,4 +1,4 @@ -use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError}; +use client_core::error::ClientCoreError; use crypto::asymmetric::identity::Ed25519RecoveryError; use gateway_client::error::GatewayClientError; use validator_client::ValidatorClientError; @@ -15,9 +15,6 @@ pub enum Socks5ClientError { ValidatorClientError(#[from] ValidatorClientError), #[error("client-core error: {0}")] ClientCoreError(#[from] ClientCoreError), - #[error("Reply key storage error: {0}")] - ReplyKeyStorageError(#[from] ReplyKeyStorageError), - #[error("Failed to load config for: {0}")] FailedToLoadConfig(String), #[error("Failed local version check, client and config mismatch")] diff --git a/clients/socks5/src/socks/client.rs b/clients/socks5/src/socks/client.rs index df73d9ee7f..0b4c07b597 100644 --- a/clients/socks5/src/socks/client.rs +++ b/clients/socks5/src/socks/client.rs @@ -126,11 +126,33 @@ impl AsyncWrite for StreamState { } } +#[derive(Debug, Copy, Clone)] +pub(crate) struct Config { + use_surbs_for_responses: bool, + connection_start_surbs: u32, + per_request_surbs: u32, +} + +impl Config { + pub(crate) fn new( + use_surbs_for_responses: bool, + connection_start_surbs: u32, + per_request_surbs: u32, + ) -> Self { + Self { + use_surbs_for_responses, + connection_start_surbs, + per_request_surbs, + } + } +} + /// A client connecting to the Socks proxy server, because /// it wants to make a Nym-protected outbound request. Typically, this is /// something like e.g. a wallet app running on your laptop connecting to /// SphinxSocksServer. pub(crate) struct SocksClient { + config: Config, controller_sender: ControllerSender, stream: StreamState, auth_nmethods: u8, @@ -161,6 +183,7 @@ impl SocksClient { /// Create a new SOCKClient #[allow(clippy::too_many_arguments)] pub fn new( + config: Config, stream: TcpStream, authenticator: Authenticator, input_sender: InputMessageSender, @@ -172,6 +195,7 @@ impl SocksClient { ) -> Self { let connection_id = Self::generate_random(); SocksClient { + config, controller_sender, connection_id, stream: StreamState::Available(stream), @@ -230,14 +254,14 @@ impl SocksClient { } } - async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { - let req = Request::new_connect(self.connection_id, remote_address, self.self_address); + async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { + let req = Request::new_connect(self.connection_id, remote_address, None); let msg = Message::Request(req); - let input_message = InputMessage::new_fresh( + let input_message = InputMessage::new_anonymous( self.service_provider, msg.into_bytes(), - false, + self.config.connection_start_surbs, TransmissionLane::ConnectionId(self.connection_id), ); self.input_sender @@ -246,6 +270,30 @@ impl SocksClient { .expect("InputMessageReceiver has stopped receiving!"); } + async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) { + let req = Request::new_connect(self.connection_id, remote_address, Some(self.self_address)); + let msg = Message::Request(req); + + let input_message = InputMessage::new_regular( + self.service_provider, + msg.into_bytes(), + TransmissionLane::ConnectionId(self.connection_id), + ); + self.input_sender + .send(input_message) + .await + .expect("InputMessageReceiver has stopped receiving!"); + } + + async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { + if self.config.use_surbs_for_responses { + self.send_anonymous_connect_to_mixnet(remote_address).await + } else { + self.send_connect_to_mixnet_with_return_address(remote_address) + .await + } + } + async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) { self.send_connect_to_mixnet(remote_proxy_target.clone()) .await; @@ -257,6 +305,8 @@ impl SocksClient { .to_string(); let connection_id = self.connection_id; let input_sender = self.input_sender.clone(); + let anonymous = self.config.use_surbs_for_responses; + let per_request_surbs = self.config.per_request_surbs; let recipient = self.service_provider; let (stream, _) = ProxyRunner::new( @@ -273,7 +323,16 @@ impl SocksClient { let provider_request = Request::new_send(conn_id, read_data, socket_closed); let provider_message = Message::Request(provider_request); let lane = TransmissionLane::ConnectionId(conn_id); - InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane) + if anonymous { + InputMessage::new_anonymous( + recipient, + provider_message.into_bytes(), + per_request_surbs, + lane, + ) + } else { + InputMessage::new_regular(recipient, provider_message.into_bytes(), lane) + } }) .await .into_inner(); diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index 760ec36d24..b59a805583 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -52,8 +52,8 @@ impl MixnetResponseListener { async fn on_message(&self, reconstructed_message: ReconstructedMessage) { let raw_message = reconstructed_message.message; - if reconstructed_message.reply_surb.is_some() { - warn!("this message had a surb - we didn't do anything with it"); + if reconstructed_message.sender_tag.is_some() { + warn!("this message was sent anonymously - it couldn't have come from the service provider"); } let response = match Message::try_from_bytes(&raw_message) { diff --git a/clients/socks5/src/socks/mod.rs b/clients/socks5/src/socks/mod.rs index 20dd99f125..236e5391ec 100644 --- a/clients/socks5/src/socks/mod.rs +++ b/clients/socks5/src/socks/mod.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] pub mod authentication; -mod client; +pub(crate) mod client; pub(crate) mod mixnet_responses; mod request; pub mod server; diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index f61d6052a0..010c605154 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -4,6 +4,7 @@ use super::{ mixnet_responses::MixnetResponseListener, types::{ResponseCode, SocksProxyError}, }; +use crate::socks::client; use client_connections::{ConnectionCommandSender, LaneQueueLengths}; use client_core::client::{ inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender, @@ -21,6 +22,7 @@ pub struct SphinxSocksServer { listening_address: SocketAddr, service_provider: Recipient, self_address: Recipient, + client_config: client::Config, lane_queue_lengths: LaneQueueLengths, shutdown: ShutdownListener, } @@ -33,6 +35,7 @@ impl SphinxSocksServer { service_provider: Recipient, self_address: Recipient, lane_queue_lengths: LaneQueueLengths, + client_config: client::Config, shutdown: ShutdownListener, ) -> Self { // hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can @@ -44,6 +47,7 @@ impl SphinxSocksServer { listening_address: format!("{}:{}", ip, port).parse().unwrap(), service_provider, self_address, + client_config, lane_queue_lengths, shutdown, } @@ -85,6 +89,7 @@ impl SphinxSocksServer { Ok((stream, _remote)) = listener.accept() => { // TODO Optimize this let mut client = SocksClient::new( + self.client_config, stream, self.authenticator.clone(), input_sender.clone(), diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index 2b8b8bd644..f76918463d 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -103,6 +103,29 @@ pub struct Debug { /// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. pub use_extended_packet_size: bool, + + /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. + /// It can only allow to go below that value if its to request additional reply surbs. + pub minimum_reply_surb_storage_threshold: usize, + + /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. + pub maximum_reply_surb_storage_threshold: usize, + + /// Defines the minimum number of reply surbs the client would request. + pub minimum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs the client would request. + pub maximum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. + pub maximum_allowed_reply_surb_request_size: u32, + + /// Defines the amount of reply surbs that the client is going to request when it runs out while attempting to retransmit packets. + pub retransmission_reply_surb_request_size: u32, + + /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking + /// for more even though in theory they wouldn't need to. + pub maximum_reply_surb_waiting_period_ms: u64, } impl From for ConfigDebug { @@ -132,6 +155,15 @@ impl From for ConfigDebug { disable_main_poisson_packet_distribution: debug .disable_main_poisson_packet_distribution, use_extended_packet_size, + minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold, + maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold, + minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size, + maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size, + maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size, + retransmission_reply_surb_request_size: debug.retransmission_reply_surb_request_size, + maximum_reply_surb_waiting_period: Duration::from_millis( + debug.maximum_reply_surb_waiting_period_ms, + ), } } } @@ -154,6 +186,15 @@ impl From for Debug { disable_main_poisson_packet_distribution: debug .disable_main_poisson_packet_distribution, use_extended_packet_size: debug.use_extended_packet_size.is_some(), + minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold, + maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold, + minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size, + maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size, + maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size, + retransmission_reply_surb_request_size: debug.retransmission_reply_surb_request_size, + maximum_reply_surb_waiting_period_ms: debug + .maximum_reply_surb_waiting_period + .as_millis() as u64, } } } diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index c64dbe69e0..7854c01627 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -3,6 +3,11 @@ use self::config::Config; use client_connections::{ConnectionCommandReceiver, LaneQueueLengths, TransmissionLane}; +use client_core::client::replies::reply_controller; +use client_core::client::replies::reply_controller::{ + ReplyControllerReceiver, ReplyControllerSender, +}; +use client_core::client::replies::reply_storage::{CombinedReplyStorage, SentReplyKeys}; use client_core::client::{ cover_traffic_stream::LoopCoverTrafficStream, inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender}, @@ -116,34 +121,33 @@ impl NymClient { ); if let Some(size) = &self.config.debug.use_extended_packet_size { - stream.set_custom_packet_size(size.clone().into()); + stream.set_custom_packet_size((*size).into()); } stream.start(); } + #[allow(clippy::too_many_arguments)] fn start_real_traffic_controller( &self, topology_accessor: TopologyAccessor, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, mix_sender: BatchMixMessageSender, - client_connection_rx: ConnectionCommandReceiver, + reply_storage: CombinedReplyStorage, + reply_controller_sender: ReplyControllerSender, + reply_controller_receiver: ReplyControllerReceiver, lane_queue_lengths: LaneQueueLengths, + client_connection_rx: ConnectionCommandReceiver, ) { let mut controller_config = real_messages_control::Config::new( + &self.config.debug, self.key_manager.ack_key(), - self.config.debug.ack_wait_multiplier, - self.config.debug.ack_wait_addition, - self.config.debug.average_ack_delay, - self.config.debug.message_sending_average_delay, - self.config.debug.average_packet_delay, - self.config.debug.disable_main_poisson_packet_distribution, self.as_mix_recipient(), ); if let Some(size) = &self.config.debug.use_extended_packet_size { - controller_config.set_custom_packet_size(size.clone().into()); + controller_config.set_custom_packet_size((*size).into()); } console_log!("Starting real traffic stream..."); @@ -154,6 +158,9 @@ impl NymClient { input_receiver, mix_sender, topology_accessor, + reply_storage, + reply_controller_sender, + reply_controller_receiver, lane_queue_lengths, client_connection_rx, ) @@ -166,12 +173,16 @@ impl NymClient { &self, query_receiver: ReceivedBufferRequestReceiver, mixnet_receiver: MixnetMessageReceiver, + reply_key_storage: SentReplyKeys, + reply_controller_sender: ReplyControllerSender, ) { console_log!("Starting received messages buffer controller..."); ReceivedMessagesBufferController::new( self.key_manager.encryption_keypair(), query_receiver, mixnet_receiver, + reply_key_storage, + reply_controller_sender, ) .start() } @@ -299,8 +310,8 @@ impl NymClient { .expect("on binary message failed!"); } if let Some(ref callback) = on_message { - if msg.reply_surb.is_some() { - console_log!("the received message contained a reply-surb that we do not know how to handle (yet)") + if msg.sender_tag.is_some() { + console_log!("the received message contained a sender_tag meaning it also contained some reply surbs, but we do not know how to handle them (yet)") } let stringified = String::from_utf8_lossy(&msg.message).into_owned(); let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap(); @@ -332,6 +343,22 @@ impl NymClient { let (ack_sender, ack_receiver) = mpsc::unbounded(); let shared_topology_accessor = TopologyAccessor::new(); + // channels responsible for dealing with reply-related fun + let (reply_controller_sender, reply_controller_receiver) = + reply_controller::new_control_channels(); + + // ===================== + // ===================== + // ======TEMPORARY====== + // ===================== + // ===================== + // TODO: lower the value and improve the reliability when it's low (because it should still work in that case) + // (it goes insane at 10,200) + let reply_storage = CombinedReplyStorage::new( + self.config.debug.minimum_reply_surb_storage_threshold, + self.config.debug.maximum_reply_surb_storage_threshold, + ); + // Channel that the real traffix controller can listed to for closing connections. // Currently unused in the wasm client. let (_client_connection_tx, client_connection_rx) = mpsc::unbounded(); @@ -343,6 +370,8 @@ impl NymClient { self.start_received_messages_buffer_controller( received_buffer_request_receiver, mixnet_messages_receiver, + reply_storage.key_storage(), + reply_controller_sender.clone(), ); let gateway_client = self @@ -364,8 +393,11 @@ impl NymClient { ack_receiver, input_receiver, sphinx_message_sender.clone(), - client_connection_rx, + reply_storage, + reply_controller_sender, + reply_controller_receiver, shared_lane_queue_lengths, + client_connection_rx, ); if !self.config.debug.disable_loop_cover_traffic_stream { @@ -393,7 +425,7 @@ impl NymClient { let recipient = Recipient::try_from_base58_string(recipient).unwrap(); let lane = TransmissionLane::General; - let input_msg = InputMessage::new_fresh(recipient, message, false, lane); + let input_msg = InputMessage::new_regular(recipient, message, lane); self.input_tx .as_ref() diff --git a/common/client-connections/src/lib.rs b/common/client-connections/src/lib.rs index 515938ed54..f86270da63 100644 --- a/common/client-connections/src/lib.rs +++ b/common/client-connections/src/lib.rs @@ -10,9 +10,11 @@ pub type ConnectionId = u64; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum TransmissionLane { General, - Reply, + // we need to treat surb-related requests and responses at higher priority + // so that the rest of underlying communication could actually continue + ReplySurbRequest, + AdditionalReplySurbs, Retransmission, - Control, ConnectionId(ConnectionId), } diff --git a/common/mixnode-common/src/packet_processor/processor.rs b/common/mixnode-common/src/packet_processor/processor.rs index 918e001fec..aaadfe0171 100644 --- a/common/mixnode-common/src/packet_processor/processor.rs +++ b/common/mixnode-common/src/packet_processor/processor.rs @@ -161,7 +161,7 @@ impl SphinxPacketProcessor { ) -> Result { match packet { ProcessedPacket::ForwardHop(packet, address, delay) => { - self.process_forward_hop(packet, address, delay, packet_mode) + self.process_forward_hop(*packet, address, delay, packet_mode) } // right now there's no use for the surb_id included in the header - probably it should get removed from the // sphinx all together? diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index f0f24ac665..d9d81aecd4 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand_distr = "0.3" +thiserror = "1.0.37" nymsphinx-acknowledgements = { path = "acknowledgements" } nymsphinx-addressing = { path = "addressing" } diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index 22bd4bc468..67e863d1f6 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -72,6 +72,10 @@ impl SurbAck { PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN } + pub fn expected_total_delay(&self) -> Delay { + self.expected_total_delay + } + pub fn prepare_for_sending(self) -> (Delay, Vec) { // SURB_FIRST_HOP || SURB_ACK let surb_bytes: Vec<_> = self diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index a67849b128..7a85486e4f 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys nymsphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient` +thiserror = "1.0.37" [dev-dependencies] rand = "0.7" diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index 819f272efd..fd7235a9b7 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -10,6 +10,7 @@ use nymsphinx_types::Destination; use serde::de::{Error as SerdeError, Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{self, Formatter}; +use thiserror::Error; // Not entirely sure whether this is the correct place for those, but let's see how it's going // to work out @@ -19,46 +20,25 @@ const CLIENT_ENCRYPTION_KEY_SIZE: usize = encryption::PUBLIC_KEY_SIZE; pub type ClientIdentity = identity::PublicKey; const CLIENT_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH; -#[derive(Debug)] +pub type RecipientBytes = [u8; Recipient::LEN]; + +#[derive(Debug, Error)] pub enum RecipientFormattingError { - MalformedRecipientError, - MalformedIdentityError(identity::Ed25519RecoveryError), - MalformedEncryptionKeyError(encryption::KeyRecoveryError), - MalformedGatewayError(identity::Ed25519RecoveryError), -} + #[error("recipient is malformed - {reason} ")] + MalformedRecipientError { reason: String }, -impl fmt::Display for RecipientFormattingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { - match self { - RecipientFormattingError::MalformedRecipientError => { - write!(f, "recipient is malformed") - } - RecipientFormattingError::MalformedIdentityError(id_err) => { - write!(f, "recipient's identity key is malformed: {}", id_err) - } - RecipientFormattingError::MalformedEncryptionKeyError(enc_err) => { - write!(f, "recipient's encryption key is malformed: {}", enc_err) - } - RecipientFormattingError::MalformedGatewayError(id_err) => write!( - f, - "recipient gateway's identity key is malformed: {}", - id_err - ), - } - } -} + #[error("recipient's identity key is malformed: {0}")] + MalformedIdentityError(identity::Ed25519RecoveryError), -// since we have Debug and Display might as well slap Error on top of it too -impl std::error::Error for RecipientFormattingError {} + #[error("recipient's encryption key is malformed: {0}")] + MalformedEncryptionKeyError(#[from] encryption::KeyRecoveryError), -impl From for RecipientFormattingError { - fn from(err: encryption::KeyRecoveryError) -> Self { - RecipientFormattingError::MalformedEncryptionKeyError(err) - } + #[error("recipient gateway's identity key is malformed: {0}")] + MalformedGatewayError(identity::Ed25519RecoveryError), } // TODO: this should a different home... somewhere, but where? -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Recipient { client_identity: ClientIdentity, client_encryption_key: ClientEncryptionKey, @@ -154,7 +134,7 @@ impl Recipient { &self.gateway } - pub fn to_bytes(self) -> [u8; Self::LEN] { + pub fn to_bytes(self) -> RecipientBytes { let mut out = [0u8; Self::LEN]; out[..CLIENT_IDENTITY_SIZE].copy_from_slice(&self.client_identity.to_bytes()); out[CLIENT_IDENTITY_SIZE..CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE] @@ -165,7 +145,7 @@ impl Recipient { out } - pub fn try_from_bytes(bytes: [u8; Self::LEN]) -> Result { + pub fn try_from_bytes(bytes: RecipientBytes) -> Result { let identity_bytes = &bytes[..CLIENT_IDENTITY_SIZE]; let enc_key_bytes = &bytes[CLIENT_IDENTITY_SIZE..CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE]; @@ -196,14 +176,20 @@ impl Recipient { let string_address = full_address.into(); let split: Vec<_> = string_address.split('@').collect(); if split.len() != 2 { - return Err(RecipientFormattingError::MalformedRecipientError); + return Err(RecipientFormattingError::MalformedRecipientError { + reason: "the string address does not contain exactly a single '@' character" + .to_string(), + }); } let client_half = split[0]; let gateway_half = split[1]; let split_client: Vec<_> = client_half.split('.').collect(); if split_client.len() != 2 { - return Err(RecipientFormattingError::MalformedRecipientError); + return Err(RecipientFormattingError::MalformedRecipientError { + reason: "the string address does not contain exactly a single '.' character" + .to_string(), + }); } let client_identity = match ClientIdentity::from_base58_string(split_client[0]) { diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index 246f6de509..4fba82b3be 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" rand = {version = "0.7.3", features = ["wasm-bindgen"]} bs58 = "0.4" serde = "1.0" +thiserror = "1" crypto = { path = "../../crypto" } nymsphinx-addressing = { path = "../addressing" } diff --git a/common/nymsphinx/anonymous-replies/src/encryption_key.rs b/common/nymsphinx/anonymous-replies/src/encryption_key.rs index b52b8cea60..7a6689f4a7 100644 --- a/common/nymsphinx/anonymous-replies/src/encryption_key.rs +++ b/common/nymsphinx/anonymous-replies/src/encryption_key.rs @@ -16,7 +16,7 @@ pub type EncryptionKeyDigest = pub type SurbEncryptionKeySize = ::KeySize; -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct SurbEncryptionKey(CipherKey); #[derive(Debug)] diff --git a/common/nymsphinx/anonymous-replies/src/lib.rs b/common/nymsphinx/anonymous-replies/src/lib.rs index ce93c4099b..3e48d0ea1b 100644 --- a/common/nymsphinx/anonymous-replies/src/lib.rs +++ b/common/nymsphinx/anonymous-replies/src/lib.rs @@ -3,6 +3,7 @@ pub mod encryption_key; pub mod reply_surb; +pub mod requests; pub use encryption_key::{SurbEncryptionKey, SurbEncryptionKeySize}; pub use reply_surb::{ReplySurb, ReplySurbError}; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index b8b8ec04cf..1fb29b2910 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::encryption_key::{SurbEncryptionKey, SurbEncryptionKeyError, SurbEncryptionKeySize}; @@ -14,44 +14,22 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; use std::fmt::{self, Formatter}; use std::time; +use thiserror::Error; use topology::{NymTopology, NymTopologyError}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ReplySurbError { + #[error("tried to use reply SURB with an unpadded message")] UnpaddedMessageError, - MalformedStringError(bs58::decode::Error), - RecoveryError(SphinxError), - InvalidEncryptionKeyData(SurbEncryptionKeyError), -} -impl fmt::Display for ReplySurbError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { - match self { - ReplySurbError::UnpaddedMessageError => { - write!(f, "tried to use reply SURB with an unpadded message") - } - ReplySurbError::MalformedStringError(decode_err) => { - write!(f, "reply SURB is incorrectly formatted: {}", decode_err) - } - ReplySurbError::RecoveryError(sphinx_err) => { - write!(f, "failed to recover reply SURB from bytes: {}", sphinx_err) - } - ReplySurbError::InvalidEncryptionKeyData(surb_key_err) => write!( - f, - "failed to recover reply SURB encryption key from bytes: {}", - surb_key_err - ), - } - } -} + #[error("reply SURB is incorrectly formatted: {0}")] + MalformedStringError(#[from] bs58::decode::Error), -// since we have Debug and Display might as well slap Error on top of it too -impl std::error::Error for ReplySurbError {} + #[error("failed to recover reply SURB from bytes: {0}")] + RecoveryError(#[from] SphinxError), -impl From for ReplySurbError { - fn from(err: SurbEncryptionKeyError) -> Self { - ReplySurbError::InvalidEncryptionKeyData(err) - } + #[error("failed to recover reply SURB encryption key from bytes: {0}")] + InvalidEncryptionKeyData(#[from] SurbEncryptionKeyError), } #[derive(Debug)] @@ -157,6 +135,8 @@ impl ReplySurb { } pub fn from_bytes(bytes: &[u8]) -> Result { + // TODO: introduce bound checks to guard us against out of bound reads + let encryption_key = SurbEncryptionKey::try_from_bytes(&bytes[..SurbEncryptionKeySize::USIZE])?; @@ -189,21 +169,22 @@ impl ReplySurb { // - surb-ack // - key digest // - encrypted plaintext with padding to constant length - pub fn apply_surb( + pub fn apply_surb>( self, - message: &[u8], + message: M, packet_size: Option, ) -> Result<(SphinxPacket, NymNodeRoutingAddress), ReplySurbError> { let packet_size = packet_size.unwrap_or_default(); - if message.len() != packet_size.plaintext_size() { + let message_bytes = message.as_ref(); + if message_bytes.len() != packet_size.plaintext_size() { return Err(ReplySurbError::UnpaddedMessageError); } // this can realistically only fail on too long messages and we just checked for that let (packet, first_hop) = self .surb - .use_surb(message, packet_size.payload_size()) + .use_surb(message_bytes, packet_size.payload_size()) .expect("this error indicates inconsistent message length checking - it shouldn't have happened!"); let first_hop_address = NymNodeRoutingAddress::try_from(first_hop).unwrap(); diff --git a/common/nymsphinx/anonymous-replies/src/requests.rs b/common/nymsphinx/anonymous-replies/src/requests.rs new file mode 100644 index 0000000000..18eddb9489 --- /dev/null +++ b/common/nymsphinx/anonymous-replies/src/requests.rs @@ -0,0 +1,409 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ReplySurb, ReplySurbError}; +use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError}; +use std::fmt::{Display, Formatter}; +use std::mem; +use thiserror::Error; + +pub const SENDER_TAG_SIZE: usize = 16; +pub type AnonymousSenderTag = [u8; SENDER_TAG_SIZE]; + +#[derive(Debug, Error)] +pub enum InvalidReplyRequestError { + #[error("Did not provide sufficient number of bytes to deserialize a valid request")] + RequestTooShortToDeserialize, + + #[error("{received} is not a valid content tag for a repliable message")] + InvalidRepliableContentTag { received: u8 }, + + #[error("{received} is not a valid content tag for a reply message")] + InvalidReplyContentTag { received: u8 }, + + #[error("failed to deserialize recipient information - {0}")] + MalformedRecipient(#[from] RecipientFormattingError), + + #[error("failed to deserialize replySURB - {0}")] + MalformedReplySurb(#[from] ReplySurbError), +} + +#[derive(Debug)] +pub struct RepliableMessage { + pub sender_tag: AnonymousSenderTag, + pub content: RepliableMessageContent, +} + +impl Display for RepliableMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.content { + RepliableMessageContent::Data { + message, + reply_surbs, + } => write!( + f, + "repliable {:.2} kiB data message with {} reply surbs attached from {:?}", + message.len() as f64 / 1024.0, + reply_surbs.len(), + self.sender_tag, + ), + RepliableMessageContent::AdditionalSurbs { reply_surbs } => write!( + f, + "repliable additional surbs message ({} reply surbs attached) from {:?}", + reply_surbs.len(), + self.sender_tag, + ), + RepliableMessageContent::Heartbeat { + additional_reply_surbs, + } => { + write!( + f, + "repliable heartbeat message ({} reply surbs attached) from {:?}", + additional_reply_surbs.len(), + self.sender_tag, + ) + } + } + } +} + +impl RepliableMessage { + pub fn new_data( + data: Vec, + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + ) -> Self { + RepliableMessage { + sender_tag, + content: RepliableMessageContent::Data { + message: data, + reply_surbs, + }, + } + } + + pub fn new_additional_surbs( + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + ) -> Self { + RepliableMessage { + sender_tag, + content: RepliableMessageContent::AdditionalSurbs { reply_surbs }, + } + } + + pub fn into_bytes(self) -> Vec { + let content_tag = self.content.tag(); + + self.sender_tag + .into_iter() + .chain(std::iter::once(content_tag as u8)) + .chain(self.content.into_bytes()) + .collect() + } + + pub fn try_from_bytes( + bytes: &[u8], + num_mix_hops: u8, + ) -> Result { + if bytes.len() < SENDER_TAG_SIZE + 1 { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + let sender_tag = bytes[..SENDER_TAG_SIZE].try_into().unwrap(); + let content_tag = RepliableMessageContentTag::try_from(bytes[SENDER_TAG_SIZE])?; + + let content = RepliableMessageContent::try_from_bytes( + &bytes[SENDER_TAG_SIZE + 1..], + num_mix_hops, + content_tag, + )?; + + Ok(RepliableMessage { + sender_tag, + content, + }) + } +} + +// this recovery code is shared between all variants containing reply surbs +fn recover_reply_surbs( + bytes: &[u8], + num_mix_hops: u8, +) -> Result<(Vec, usize), InvalidReplyRequestError> { + let mut consumed = mem::size_of::(); + if bytes.len() < consumed { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + let surb_size = ReplySurb::serialized_len(num_mix_hops); + if bytes[consumed..].len() < num_surbs as usize * surb_size { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + + let mut reply_surbs = Vec::with_capacity(num_surbs as usize); + for _ in 0..num_surbs as usize { + let surb_bytes = &bytes[consumed..consumed + surb_size]; + let reply_surb = ReplySurb::from_bytes(surb_bytes)?; + reply_surbs.push(reply_surb); + + consumed += surb_size; + } + + Ok((reply_surbs, consumed)) +} + +#[repr(u8)] +enum RepliableMessageContentTag { + Data = 0, + AdditionalSurbs = 1, + Heartbeat = 2, +} + +impl TryFrom for RepliableMessageContentTag { + type Error = InvalidReplyRequestError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (RepliableMessageContentTag::Data as u8) => Ok(Self::Data), + _ if value == (RepliableMessageContentTag::AdditionalSurbs as u8) => { + Ok(Self::AdditionalSurbs) + } + _ if value == (RepliableMessageContentTag::Heartbeat as u8) => Ok(Self::Heartbeat), + val => Err(InvalidReplyRequestError::InvalidRepliableContentTag { received: val }), + } + } +} + +// sent by original sender that initialised the communication that knows address of the remote +#[derive(Debug)] +pub enum RepliableMessageContent { + Data { + message: Vec, + reply_surbs: Vec, + }, + AdditionalSurbs { + reply_surbs: Vec, + }, + Heartbeat { + additional_reply_surbs: Vec, + }, +} + +impl RepliableMessageContent { + pub fn into_bytes(self) -> Vec { + match self { + RepliableMessageContent::Data { + message, + reply_surbs, + } => { + let num_surbs = reply_surbs.len() as u32; + + num_surbs + .to_be_bytes() + .into_iter() + .chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes())) + .chain(message.into_iter()) + .collect() + } + RepliableMessageContent::AdditionalSurbs { reply_surbs } => { + let num_surbs = reply_surbs.len() as u32; + + num_surbs + .to_be_bytes() + .into_iter() + .chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes())) + .collect() + } + RepliableMessageContent::Heartbeat { + additional_reply_surbs, + } => { + let num_surbs = additional_reply_surbs.len() as u32; + + num_surbs + .to_be_bytes() + .into_iter() + .chain( + additional_reply_surbs + .into_iter() + .flat_map(|s| s.to_bytes()), + ) + .collect() + } + } + } + + fn try_from_bytes( + bytes: &[u8], + num_mix_hops: u8, + tag: RepliableMessageContentTag, + ) -> Result { + if bytes.is_empty() { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + + let (reply_surbs, n) = recover_reply_surbs(bytes, num_mix_hops)?; + + match tag { + RepliableMessageContentTag::Data => Ok(RepliableMessageContent::Data { + message: bytes[n..].to_vec(), + reply_surbs, + }), + RepliableMessageContentTag::AdditionalSurbs => { + Ok(RepliableMessageContent::AdditionalSurbs { reply_surbs }) + } + RepliableMessageContentTag::Heartbeat => Ok(RepliableMessageContent::Heartbeat { + additional_reply_surbs: reply_surbs, + }), + } + } + + fn tag(&self) -> RepliableMessageContentTag { + match self { + RepliableMessageContent::Data { .. } => RepliableMessageContentTag::Data, + RepliableMessageContent::AdditionalSurbs { .. } => { + RepliableMessageContentTag::AdditionalSurbs + } + RepliableMessageContent::Heartbeat { .. } => RepliableMessageContentTag::Heartbeat, + } + } +} + +// sent by the remote party who does **NOT** know the original sender's identity +#[derive(Debug)] +pub struct ReplyMessage { + pub content: ReplyMessageContent, +} + +impl Display for ReplyMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.content { + ReplyMessageContent::Data { message } => write!( + f, + "{:.2} kiB reply data message", + message.len() as f64 / 1024.0 + ), + ReplyMessageContent::SurbRequest { recipient, amount } => write!( + f, + "request for {amount} additional reply SURBs from {recipient}", + ), + } + } +} + +impl ReplyMessage { + pub fn new_data_message(message: Vec) -> Self { + ReplyMessage { + content: ReplyMessageContent::Data { message }, + } + } + + pub fn new_surb_request_message(recipient: Recipient, amount: u32) -> Self { + ReplyMessage { + content: ReplyMessageContent::SurbRequest { + recipient: Box::new(recipient), + amount, + }, + } + } + + pub fn into_bytes(self) -> Vec { + let content_tag = self.content.tag(); + + std::iter::once(content_tag as u8) + .chain(self.content.into_bytes()) + .collect() + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + let tag = ReplyMessageContentTag::try_from(bytes[0])?; + let content = ReplyMessageContent::try_from_bytes(&bytes[1..], tag)?; + + Ok(ReplyMessage { content }) + } +} + +#[repr(u8)] +enum ReplyMessageContentTag { + Data = 0, + SurbRequest = 1, +} + +impl TryFrom for ReplyMessageContentTag { + type Error = InvalidReplyRequestError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (ReplyMessageContentTag::Data as u8) => Ok(Self::Data), + _ if value == (ReplyMessageContentTag::SurbRequest as u8) => Ok(Self::SurbRequest), + val => Err(InvalidReplyRequestError::InvalidReplyContentTag { received: val }), + } + } +} + +#[derive(Debug)] +pub enum ReplyMessageContent { + // TODO: later allow to request surbs whilst sending data + Data { + message: Vec, + }, + SurbRequest { + recipient: Box, + amount: u32, + }, +} + +impl ReplyMessageContent { + pub fn into_bytes(self) -> Vec { + match self { + // TODO: a lot of unnecessary allocations + ReplyMessageContent::Data { message } => message.into_iter().collect(), + ReplyMessageContent::SurbRequest { recipient, amount } => recipient + .to_bytes() + .into_iter() + .chain(amount.to_be_bytes().into_iter()) + .collect(), + } + } + + fn try_from_bytes( + bytes: &[u8], + tag: ReplyMessageContentTag, + ) -> Result { + if bytes.is_empty() { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + + match tag { + ReplyMessageContentTag::Data => Ok(ReplyMessageContent::Data { + message: bytes.to_vec(), + }), + ReplyMessageContentTag::SurbRequest => { + if bytes.len() != Recipient::LEN + std::mem::size_of::() { + return Err(InvalidReplyRequestError::RequestTooShortToDeserialize); + } + let mut recipient_bytes = [0u8; Recipient::LEN]; + recipient_bytes.copy_from_slice(&bytes[..Recipient::LEN]); + + Ok(ReplyMessageContent::SurbRequest { + recipient: Box::new(Recipient::try_from_bytes(recipient_bytes)?), + amount: u32::from_be_bytes([ + bytes[Recipient::LEN], + bytes[Recipient::LEN + 1], + bytes[Recipient::LEN + 2], + bytes[Recipient::LEN + 3], + ]), + }) + } + } + } + + fn tag(&self) -> ReplyMessageContentTag { + match self { + ReplyMessageContent::Data { .. } => ReplyMessageContentTag::Data, + ReplyMessageContent::SurbRequest { .. } => ReplyMessageContentTag::SurbRequest, + } + } +} diff --git a/common/nymsphinx/chunking/Cargo.toml b/common/nymsphinx/chunking/Cargo.toml index 5f47ab5358..3d6bacab61 100644 --- a/common/nymsphinx/chunking/Cargo.toml +++ b/common/nymsphinx/chunking/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] log = "0.4.8" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +thiserror = "1.0.37" nymsphinx-addressing = { path = "../addressing" } nymsphinx-params = { path = "../params" } diff --git a/common/nymsphinx/chunking/src/fragment.rs b/common/nymsphinx/chunking/src/fragment.rs index 58c1baa78a..9be5e6a09b 100644 --- a/common/nymsphinx/chunking/src/fragment.rs +++ b/common/nymsphinx/chunking/src/fragment.rs @@ -1,10 +1,8 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::set::generate_set_id; use crate::ChunkingError; use nymsphinx_params::{SerializedFragmentIdentifier, FRAG_ID_LEN}; -use rand::Rng; use std::convert::TryInto; use std::fmt::{self, Formatter}; @@ -77,15 +75,6 @@ impl fmt::Display for FragmentIdentifier { } impl FragmentIdentifier { - // I really dislike how 'hacky' this function seems - // refer to: https://github.com/nymtech/nym/issues/294 for further discussion - pub fn new_reply(rng: &mut R) -> Self { - FragmentIdentifier { - set_id: generate_set_id(rng), - fragment_position: 0, - } - } - // and this one pub fn is_reply(self) -> bool { self.set_id > 0 && self.fragment_position == 0 @@ -110,7 +99,7 @@ impl FragmentIdentifier { let set_id = i32::from_be_bytes([b[0], b[1], b[2], b[3]]); // set_id == 0 is valid for COVER_FRAG_ID and replies if set_id < 0 { - return Err(ChunkingError::MalformedFragmentIdentifier); + return Err(ChunkingError::MalformedFragmentIdentifier { received: set_id }); } Ok(FragmentIdentifier { @@ -153,24 +142,42 @@ impl Fragment { // check for whether payload has expected length, which depend on whether fragment is linked // and if it's the only one or the last one in the set (then lower bound is removed) + let max_linked_len = linked_fragment_payload_max_len(max_plaintext_size); + let max_unlinked_len = unlinked_fragment_payload_max_len(max_plaintext_size); + if previous_fragments_set_id.is_some() { if total_fragments > 1 { - if payload.len() != linked_fragment_payload_max_len(max_plaintext_size) { - return Err(ChunkingError::InvalidPayloadLengthError); + if payload.len() != max_linked_len { + return Err(ChunkingError::InvalidPayloadLengthError { + received: payload.len(), + expected: max_linked_len, + }); } - } else if payload.len() > linked_fragment_payload_max_len(max_plaintext_size) { - return Err(ChunkingError::InvalidPayloadLengthError); + } else if payload.len() > max_linked_len { + return Err(ChunkingError::TooLongPayloadLengthError { + received: payload.len(), + expected_at_most: max_linked_len, + }); } } else if next_fragments_set_id.is_some() { - if payload.len() != linked_fragment_payload_max_len(max_plaintext_size) { - return Err(ChunkingError::InvalidPayloadLengthError); + if payload.len() != max_linked_len { + return Err(ChunkingError::InvalidPayloadLengthError { + received: payload.len(), + expected: max_linked_len, + }); } } else if total_fragments != current_fragment { - if payload.len() != unlinked_fragment_payload_max_len(max_plaintext_size) { - return Err(ChunkingError::InvalidPayloadLengthError); + if payload.len() != max_unlinked_len { + return Err(ChunkingError::InvalidPayloadLengthError { + received: payload.len(), + expected: max_unlinked_len, + }); } - } else if payload.len() > unlinked_fragment_payload_max_len(max_plaintext_size) { - return Err(ChunkingError::InvalidPayloadLengthError); + } else if payload.len() > max_unlinked_len { + return Err(ChunkingError::TooLongPayloadLengthError { + received: payload.len(), + expected_at_most: max_unlinked_len, + }); } Ok(Fragment { @@ -347,7 +354,10 @@ impl FragmentHeader { fn try_from_bytes(b: &[u8]) -> Result<(Self, usize), ChunkingError> { // header needs to be at least 7 bytes long if b.len() < UNLINKED_FRAGMENTED_HEADER_LEN { - return Err(ChunkingError::TooShortFragmentData); + return Err(ChunkingError::TooShortFragmentHeader { + received: b.len(), + expected: UNLINKED_FRAGMENTED_HEADER_LEN, + }); } let frag_id = i32::from_be_bytes(b[0..4].try_into().unwrap()); // sanity check for the fragmentation flag @@ -370,7 +380,10 @@ impl FragmentHeader { let read_bytes = if b[6] != 0 { // there's linking ID supposedly attached, make sure we have enough bytes to parse if b.len() < LINKED_FRAGMENTED_HEADER_LEN { - return Err(ChunkingError::TooShortFragmentData); + return Err(ChunkingError::TooShortFragmentHeader { + received: b.len(), + expected: LINKED_FRAGMENTED_HEADER_LEN, + }); } let flagged_linked_id = i32::from_be_bytes(b[6..10].try_into().unwrap()); diff --git a/common/nymsphinx/chunking/src/lib.rs b/common/nymsphinx/chunking/src/lib.rs index 62a246955b..60316edb15 100644 --- a/common/nymsphinx/chunking/src/lib.rs +++ b/common/nymsphinx/chunking/src/lib.rs @@ -3,6 +3,7 @@ use crate::fragment::{linked_fragment_payload_max_len, unlinked_fragment_payload_max_len}; pub use set::split_into_sets; +use thiserror::Error; // Future consideration: currently in a lot of places, the payloads have randomised content // which is not a perfect testing strategy as it might not detect some edge cases I never would @@ -45,18 +46,26 @@ pub mod set; /// Both of those concepts as well as their structures, i.e. `Set` and `Fragment` /// are further explained in the respective files. -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Error)] pub enum ChunkingError { - InvalidPayloadLengthError, - TooBigMessageToSplit, + #[error("Received payload is too long. Got {received}, expected {expected}")] + InvalidPayloadLengthError { received: usize, expected: usize }, + + #[error("Received payload is too long. Got {received}, expected at most {expected_at_most}")] + TooLongPayloadLengthError { + received: usize, + expected_at_most: usize, + }, + + // this should really be split into multiple variants to provide better error information + #[error("Provided header was malformed or contained self-contradicting fields")] MalformedHeaderError, - NoValidProvidersError, - NoValidRoutesAvailableError, - InvalidTopologyError, - TooShortFragmentData, - MalformedFragmentData, - UnexpectedFragmentCount, - MalformedFragmentIdentifier, + + #[error("Received too few bytes to deserialize fragment header. Got {received}, expected {expected}")] + TooShortFragmentHeader { received: usize, expected: usize }, + + #[error("Received fragment identifier ({received}) is not a valid value!")] + MalformedFragmentIdentifier { received: i32 }, } /// Returns number of fragments the message will be split to as well as number of available @@ -94,7 +103,7 @@ pub fn number_of_required_fragments( // we know for sure that all fragments in all but last set are definitely full // (last one has single 'linked' fragment) - let without_last = (n - 1) * (u8::max_value() as usize); + let without_last = (n - 1) * (u8::MAX as usize); let linked_fragments_without_last = (2 * n - 2) - 1; let unlinked_fragments_without_last = without_last - linked_fragments_without_last; diff --git a/common/nymsphinx/src/lib.rs b/common/nymsphinx/src/lib.rs index e3e7c51fac..bbc69c2682 100644 --- a/common/nymsphinx/src/lib.rs +++ b/common/nymsphinx/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub mod message; pub mod preparer; pub mod receiver; pub mod utils; @@ -16,3 +17,6 @@ pub use nymsphinx_forwarding as forwarding; pub use nymsphinx_framing as framing; pub use nymsphinx_params as params; pub use nymsphinx_types::*; + +// TEMP UNTIL FURTHER REFACTORING +pub use preparer::payload::NymsphinxPayloadBuilder; diff --git a/common/nymsphinx/src/message.rs b/common/nymsphinx/src/message.rs new file mode 100644 index 0000000000..f9b32daa35 --- /dev/null +++ b/common/nymsphinx/src/message.rs @@ -0,0 +1,238 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::chunking; +use crypto::asymmetric::encryption; +use crypto::Digest; +use nymsphinx_addressing::clients::Recipient; +use nymsphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; +use nymsphinx_anonymous_replies::requests::{ + InvalidReplyRequestError, RepliableMessage, RepliableMessageContent, ReplyMessage, + ReplyMessageContent, +}; +use nymsphinx_chunking::fragment::Fragment; +use nymsphinx_params::{PacketSize, ReplySurbKeyDigestAlgorithm}; +use rand::Rng; +use std::fmt::{Display, Formatter}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NymMessageError { + #[error("{received} is not a valid type tag for a NymMessage")] + InvalidMessageType { received: u8 }, + + #[error(transparent)] + InvalidReplyRequest(#[from] InvalidReplyRequestError), + + #[error("The received message seems to have incorrect zero padding (no '1' byte found)")] + InvalidMessagePadding, + + #[error("Received empty message for deserialization")] + EmptyMessage, +} + +#[repr(u8)] +enum NymMessageType { + Plain = 0, + Repliable = 1, + Reply = 2, +} + +impl TryFrom for NymMessageType { + type Error = NymMessageError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (NymMessageType::Plain as u8) => Ok(Self::Plain), + _ if value == (NymMessageType::Repliable as u8) => Ok(Self::Repliable), + _ if value == (NymMessageType::Reply as u8) => Ok(Self::Reply), + val => Err(NymMessageError::InvalidMessageType { received: val }), + } + } +} + +pub type PlainMessage = Vec; + +#[derive(Debug)] +pub enum NymMessage { + Plain(PlainMessage), + Repliable(RepliableMessage), + Reply(ReplyMessage), +} + +impl Display for NymMessage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NymMessage::Plain(plain_message) => write!( + f, + "plain {:.2} kiB message", + plain_message.len() as f64 / 1024.0 + ), + NymMessage::Repliable(repliable_message) => repliable_message.fmt(f), + NymMessage::Reply(reply_message) => reply_message.fmt(f), + } + } +} + +impl NymMessage { + pub fn new_additional_surbs_request(recipient: Recipient, amount: u32) -> Self { + NymMessage::Reply(ReplyMessage { + content: ReplyMessageContent::SurbRequest { + recipient: Box::new(recipient), + amount, + }, + }) + } + + pub fn new_plain(msg: Vec) -> Self { + NymMessage::Plain(msg) + } + + pub fn new_repliable(msg: RepliableMessage) -> Self { + NymMessage::Repliable(msg) + } + + pub fn new_reply(msg: ReplyMessage) -> Self { + NymMessage::Reply(msg) + } + + pub fn is_reply_surb_request(&self) -> bool { + match self { + NymMessage::Reply(reply_msg) => { + matches!(reply_msg.content, ReplyMessageContent::SurbRequest { .. }) + } + _ => false, + } + } + + pub fn into_inner_data(self) -> Vec { + match self { + NymMessage::Plain(data) => data, + NymMessage::Repliable(repliable) => match repliable.content { + RepliableMessageContent::Data { message, .. } => message, + _ => Vec::new(), + }, + NymMessage::Reply(reply) => match reply.content { + ReplyMessageContent::Data { message } => message, + _ => Vec::new(), + }, + } + } + + fn typ(&self) -> NymMessageType { + match self { + NymMessage::Plain(_) => NymMessageType::Plain, + NymMessage::Repliable(_) => NymMessageType::Repliable, + NymMessage::Reply(_) => NymMessageType::Reply, + } + } + + fn inner_bytes(self) -> Vec { + match self { + NymMessage::Plain(msg) => msg, + NymMessage::Repliable(msg) => msg.into_bytes(), + NymMessage::Reply(msg) => msg.into_bytes(), + } + } + + // the message is in the format of: + // typ || msg + fn into_bytes(self) -> Vec { + let typ = self.typ(); + + std::iter::once(typ as u8) + .chain(self.inner_bytes()) + .collect() + } + + fn try_from_bytes(bytes: &[u8], num_mix_hops: u8) -> Result { + if bytes.is_empty() { + return Err(NymMessageError::EmptyMessage); + } + + let typ_tag = NymMessageType::try_from(bytes[0])?; + match typ_tag { + NymMessageType::Plain => Ok(NymMessage::Plain(bytes[1..].to_vec())), + NymMessageType::Repliable => Ok(NymMessage::Repliable( + RepliableMessage::try_from_bytes(&bytes[1..], num_mix_hops)?, + )), + NymMessageType::Reply => Ok(NymMessage::Reply(ReplyMessage::try_from_bytes( + &bytes[1..], + )?)), + } + } + + /// Length of plaintext (from the sphinx point of view) data that is available per sphinx + /// packet. + pub fn available_plaintext_per_packet(&self, packet_size: PacketSize) -> usize { + let ack_overhead = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size(); + + let variant_overhead = match self { + // each plain or repliable packet attaches an ephemeral public key so that the recipient + // could perform diffie-hellman with its own keys followed by a kdf to re-derive + // the packet encryption key + NymMessage::Plain(_) | NymMessage::Repliable(_) => encryption::PUBLIC_KEY_SIZE, + // each reply attaches the digest of the encryption key so that the recipient could + // lookup correct key for decryption, + NymMessage::Reply(_) => ReplySurbKeyDigestAlgorithm::output_size(), + }; + + packet_size.plaintext_size() - ack_overhead - variant_overhead + } + + /// Pads the message so that after it gets chunked, it will occupy exactly N sphinx packets. + /// Produces new_message = message || 1 || 0000.... + pub fn pad_to_full_packet_lengths(self, plaintext_per_packet: usize) -> PaddedMessage { + let bytes = self.into_bytes(); + + // 1 is added as there will always have to be at least a single byte of padding (1) added + // to be able to later distinguish the actual padding from the underlying message + let (_, space_left) = + chunking::number_of_required_fragments(bytes.len() + 1, plaintext_per_packet); + + bytes + .into_iter() + .chain(std::iter::once(1u8)) + .chain(std::iter::repeat(0u8).take(space_left)) + .collect::>() + .into() + } +} + +pub struct PaddedMessage(Vec); + +impl PaddedMessage { + pub fn new_reconstructed(bytes: Vec) -> Self { + PaddedMessage(bytes) + } + + /// Splits the padded message into [`Fragment`] that when serialized are going to become + /// sphinx packet payloads. + pub fn split_into_fragments( + self, + rng: &mut R, + plaintext_per_packet: usize, + ) -> Vec { + chunking::split_into_sets(rng, &self.0, plaintext_per_packet) + .into_iter() + .flat_map(|fragment_set| fragment_set.into_iter()) + .collect() + } + + // reverse of NymMessage::pad_to_full_packet_lengths + pub fn remove_padding(self, num_mix_hops: u8) -> Result { + // we are looking for first occurrence of 1 in the tail and we get its index + if let Some(padding_end) = self.0.iter().rposition(|b| *b == 1) { + // and now we only take bytes until that point (but not including it) + NymMessage::try_from_bytes(&self.0[..padding_end], num_mix_hops) + } else { + Err(NymMessageError::InvalidMessagePadding) + } + } +} + +impl From> for PaddedMessage { + fn from(bytes: Vec) -> Self { + PaddedMessage(bytes) + } +} diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index d91a7c85a5..f26acd913a 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -1,24 +1,17 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::chunking; -use crypto::asymmetric::encryption; -use crypto::shared_key::new_ephemeral_shared_key; -use crypto::symmetric::stream_cipher; -use crypto::Digest; +use crate::message::NymMessage; +use crate::NymsphinxPayloadBuilder; use nymsphinx_acknowledgements::surb_ack::SurbAck; use nymsphinx_acknowledgements::AckKey; use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; -use nymsphinx_anonymous_replies::encryption_key::SurbEncryptionKey; +use nymsphinx_addressing::nodes::NymNodeRoutingAddress; use nymsphinx_anonymous_replies::reply_surb::ReplySurb; use nymsphinx_chunking::fragment::{Fragment, FragmentIdentifier}; use nymsphinx_forwarding::packet::MixPacket; use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::{ - PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, - ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS, -}; +use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; use nymsphinx_types::builder::SphinxPacketBuilder; use nymsphinx_types::{delays, Delay}; use rand::{CryptoRng, Rng}; @@ -26,6 +19,8 @@ use std::convert::TryFrom; use std::time::Duration; use topology::{NymTopology, NymTopologyError}; +pub(crate) mod payload; + /// Represents fully packed and prepared [`Fragment`] that can be sent through the mix network. pub struct PreparedFragment { /// Indicates the total expected round-trip time, i.e. delay from the sending of this message @@ -38,25 +33,13 @@ pub struct PreparedFragment { pub mix_packet: MixPacket, } -#[derive(Debug)] -pub enum PreparationError { - TopologyError(NymTopologyError), - TooLongReplyMessageError, -} - -impl From for PreparationError { - fn from(err: NymTopologyError) -> Self { - PreparationError::TopologyError(err) - } -} - /// Prepares the message that is to be sent through the mix network by attaching /// an optional reply-SURB, padding it to appropriate length, encrypting its content, /// and chunking into appropriate size [`Fragment`]s. // #[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] #[derive(Clone)] #[must_use] -pub struct MessagePreparer { +pub struct MessagePreparer { /// Instance of a cryptographically secure random number generator. rng: R, @@ -115,86 +98,65 @@ where self.sender_address = sender_address; } - /// Length of plaintext (from the sphinx point of view) data that is available per sphinx - /// packet. - fn available_plaintext_per_packet(&self) -> usize { - // we need to put first hop's destination alongside the actual ack data - // TODO: a possible optimization way down the line: currently we're always assuming that - // the addresses will have `MAX_NODE_ADDRESS_UNPADDED_LEN`, i.e. be ipv6. In most cases - // they're actually going to be ipv4 hence wasting few bytes every packet. - // To fully utilise all available space, I guess first we'd need to generate routes for ACKs - // and only then perform the chunking with `available_plaintext_size` being called per chunk. - // However this will probably introduce bunch of complexity - // for relatively not a lot of gain, so it shouldn't be done just yet. - let ack_overhead = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size(); - let ephemeral_public_key_overhead = encryption::PUBLIC_KEY_SIZE; - - self.packet_size.plaintext_size() - ack_overhead - ephemeral_public_key_overhead - } - - /// Pads the message so that after it gets chunked, it will occupy exactly N sphinx packets. - /// Produces new_message = message || 1 || 0000.... - fn pad_message(&self, message: Vec) -> Vec { - // 1 is added as there will always have to be at least a single byte of padding (1) added - // to be able to later distinguish the actual padding from the underlying message - let (_, space_left) = chunking::number_of_required_fragments( - message.len() + 1, - self.available_plaintext_per_packet(), - ); - - message - .into_iter() - .chain(std::iter::once(1u8)) - .chain(std::iter::repeat(0u8).take(space_left)) - .collect() - } - - /// Attaches reply-SURB to the message alongside the reply key. - /// Results in: - /// new_message = 0 || message - /// OR - /// new_message = 1 || REPLY_KEY || REPLY_SURB || message - fn optionally_attach_reply_surb( + pub fn generate_reply_surbs( &mut self, - message: Vec, - should_attach: bool, + amount: usize, topology: &NymTopology, - ) -> Result<(Vec, Option), PreparationError> { - if should_attach { + ) -> Result, NymTopologyError> { + let mut reply_surbs = Vec::with_capacity(amount); + for _ in 0..amount { let reply_surb = ReplySurb::construct( &mut self.rng, &self.sender_address, self.average_packet_delay, topology, )?; - - let reply_key = reply_surb.encryption_key(); - // if there's a reply surb, the message takes form of `1 || REPLY_KEY || REPLY_SURB || MSG` - Ok(( - std::iter::once(true as u8) - .chain(reply_surb.to_bytes().iter().cloned()) - .chain(message.into_iter()) - .collect(), - Some(reply_key.clone()), - )) - } else { - // but if there's no reply surb, the message takes form of `0 || MSG` - Ok(( - std::iter::once(false as u8) - .chain(message.into_iter()) - .collect(), - None, - )) + reply_surbs.push(reply_surb) } + + Ok(reply_surbs) } - /// Splits the message into [`Fragment`] that are going to be put later put into sphinx packets. - fn split_message(&mut self, message: Vec) -> Vec { - let plaintext_per_packet = self.available_plaintext_per_packet(); - chunking::split_into_sets(&mut self.rng, &message, plaintext_per_packet) - .into_iter() - .flat_map(|fragment_set| fragment_set.into_iter()) - .collect() + /// The procedure is as follows: + /// For each fragment: + /// - compute SURB_ACK + /// - generate (x, g^x) + /// - obtain key k from the reply-surb which was computed as follows: + /// k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) ) + /// - compute v_b = AES-128-CTR(k, serialized_fragment) + /// - compute vk_b = H(k) || v_b + /// - compute sphinx_plaintext = SURB_ACK || H(k) || v_b + /// - compute sphinx_packet by applying the reply surb on the sphinx_plaintext + pub fn prepare_reply_chunk_for_sending( + &mut self, + fragment: Fragment, + topology: &NymTopology, + ack_key: &AckKey, + reply_surb: ReplySurb, + ) -> Result { + // TODO: pass that as an argument derived from the config + let expected_forward_delay = Delay::new_from_millis(300); + + // create an ack + let surb_ack = self.generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)?; + let ack_delay = surb_ack.expected_total_delay(); + + let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack) + .build_reply(reply_surb.encryption_key()); + + // the unwrap here is fine as the failures can only originate from attempting to use invalid payload lenghts + // and we just very carefully constructed a (presumably) valid one + let (sphinx_packet, first_hop_address) = reply_surb + .apply_surb(packet_payload, Some(self.packet_size)) + .unwrap(); + + Ok(PreparedFragment { + // the round-trip delay is the sum of delays of all hops on the forward route as + // well as the total delay of the ack packet. + // we don't know the delays inside the reply surbs so we use best-effort estimation from our poisson distribution + total_delay: expected_forward_delay + ack_delay, + mix_packet: MixPacket::new(first_hop_address, sphinx_packet, Default::default()), + }) } /// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network, @@ -222,45 +184,11 @@ where packet_recipient: &Recipient, ) -> Result { // create an ack - let (ack_delay, surb_ack_bytes) = self - .generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)? - .prepare_for_sending(); - - // TODO: - // TODO: - // TODO: - // TODO: - // TODO: ASK @AP AND @DH WHETHER THOSE KEYS CAN/SHOULD ALSO BE REUSED IN VPN MODE!! - // TODO: - // TODO: - // TODO: - // TODO: - - // create keys for 'payload' encryption - let (ephemeral_keypair, shared_key) = - new_ephemeral_shared_key::( - &mut self.rng, - packet_recipient.encryption_key(), - ); - - // serialize fragment and encrypt its content - let mut chunk_data = fragment.into_bytes(); - - let zero_iv = stream_cipher::zero_iv::(); - stream_cipher::encrypt_in_place::( - &shared_key, - &zero_iv, - &mut chunk_data, - ); - - // combine it together as follows: - // SURB_ACK_FIRST_HOP || SURB_ACK_DATA || EPHEMERAL_KEY || CHUNK_DATA - // (note: surb_ack_bytes contains SURB_ACK_FIRST_HOP || SURB_ACK_DATA ) - let packet_payload: Vec<_> = surb_ack_bytes - .into_iter() - .chain(ephemeral_keypair.public_key().to_bytes().iter().cloned()) - .chain(chunk_data.into_iter()) - .collect(); + let surb_ack = self.generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)?; + let ack_delay = surb_ack.expected_total_delay(); + + let packet_payload = NymsphinxPayloadBuilder::new(fragment, surb_ack) + .build_regular(&mut self.rng, packet_recipient.encryption_key()); // generate pseudorandom route for the packet let route = topology.random_route_to_gateway( @@ -310,113 +238,12 @@ where ) } - /// Attaches an optional reply-surb and correct padding to the underlying message - /// and splits it into [`Fragment`] that can be later packed into sphinx packets to be - /// sent through the mix network. - pub fn prepare_and_split_message( - &mut self, - message: Vec, - with_reply_surb: bool, - topology: &NymTopology, - ) -> Result<(Vec, Option), PreparationError> { - let (message, reply_key) = - self.optionally_attach_reply_surb(message, with_reply_surb, topology)?; - - let message = self.pad_message(message); - - Ok((self.split_message(message), reply_key)) - } + pub fn pad_and_split_message(&mut self, message: NymMessage) -> Vec { + let plaintext_per_packet = message.available_plaintext_per_packet(self.packet_size); - // TODO: perhaps the return type could somehow be combined with [`PreparedFragment`] ? - pub async fn prepare_reply_for_use( - &mut self, - message: Vec, - reply_surb: ReplySurb, - topology: &NymTopology, - ack_key: &AckKey, - ) -> Result<(MixPacket, FragmentIdentifier), PreparationError> { - // there's no chunking in reply-surbs so there's a hard limit on message, - // we also need to put the key digest into the message (same size as ephemeral key) - // and need 1 byte to indicate padding length (this is not the case for 'normal' messages - // as there the padding is added for the whole message) - // so before doing any processing, let's see if we have enough space for it all - let ack_overhead = MAX_NODE_ADDRESS_UNPADDED_LEN + PacketSize::AckPacket.size(); - if message.len() - > self.packet_size.plaintext_size() - - ack_overhead - - ReplySurbKeyDigestAlgorithm::output_size() - - 1 - { - return Err(PreparationError::TooLongReplyMessageError); - } - - let reply_id = FragmentIdentifier::new_reply(&mut self.rng); - - // create an ack - // even though it won't be used for retransmission, it must be present so that - // gateways could not distinguish reply packets from normal messages due to lack of said acks - // note: the ack delay is irrelevant since we do not know the delay of actual surb - let (_, surb_ack_bytes) = self - .generate_surb_ack(reply_id, topology, ack_key)? - .prepare_for_sending(); - - let zero_pad_len = self.packet_size.plaintext_size() - - message.len() - - ack_overhead - - ReplySurbKeyDigestAlgorithm::output_size() - - 1; - - // create reply message that will reach the recipient: - let mut reply_content: Vec<_> = message - .into_iter() - .chain(std::iter::once(1)) - .chain(std::iter::repeat(0).take(zero_pad_len)) - .collect(); - - // encrypt the reply message - let zero_iv = stream_cipher::zero_iv::(); - stream_cipher::encrypt_in_place::( - reply_surb.encryption_key().inner(), - &zero_iv, - &mut reply_content, - ); - - // combine it together as follows: - // SURB_ACK_FIRST_HOP || SURB_ACK_DATA || KEY_DIGEST || E (REPLY_MESSAGE || 1 || 0*) - // (note: surb_ack_bytes contains SURB_ACK_FIRST_HOP || SURB_ACK_DATA ) - let packet_payload: Vec<_> = surb_ack_bytes - .into_iter() - .chain(reply_surb.encryption_key().compute_digest().iter().copied()) - .chain(reply_content.into_iter()) - .collect(); - - // finally put it all inside a sphinx packet - // this can only fail if packet payload has incorrect size, but if it does, it means - // there's a bug in the above code - let (packet, first_hop) = reply_surb - .apply_surb(&packet_payload, Some(self.packet_size)) - .unwrap(); - - Ok(( - MixPacket::new(first_hop, packet, Default::default()), - reply_id, - )) - } - - #[allow(dead_code)] - #[cfg(test)] - pub(crate) fn test_fixture() -> MessagePreparer { - let rng = rand::rngs::OsRng; - let dummy_address = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); - - MessagePreparer { - rng, - packet_size: Default::default(), - sender_address: dummy_address, - average_packet_delay: Default::default(), - average_ack_delay: Default::default(), - num_mix_hops: DEFAULT_NUM_MIX_HOPS, - } + message + .pad_to_full_packet_lengths(plaintext_per_packet) + .split_into_fragments(&mut self.rng, plaintext_per_packet) } } diff --git a/common/nymsphinx/src/preparer/payload.rs b/common/nymsphinx/src/preparer/payload.rs new file mode 100644 index 0000000000..94acde03c8 --- /dev/null +++ b/common/nymsphinx/src/preparer/payload.rs @@ -0,0 +1,97 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crypto::aes::cipher::{KeyIvInit, StreamCipher}; +use crypto::asymmetric::encryption; +use crypto::shared_key::new_ephemeral_shared_key; +use crypto::symmetric::stream_cipher; +use crypto::symmetric::stream_cipher::CipherKey; +use nymsphinx_acknowledgements::surb_ack::SurbAck; +use nymsphinx_anonymous_replies::SurbEncryptionKey; +use nymsphinx_chunking::fragment::Fragment; +use nymsphinx_params::{ + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, +}; +use rand::{CryptoRng, RngCore}; + +pub struct NymsphinxPayloadBuilder { + fragment: Fragment, + surb_ack: SurbAck, +} + +impl NymsphinxPayloadBuilder { + pub fn new(fragment: Fragment, surb_ack: SurbAck) -> Self { + NymsphinxPayloadBuilder { fragment, surb_ack } + } + + fn build( + self, + packet_encryption_key: &CipherKey, + variant_data: impl IntoIterator, + ) -> NymsphinxPayload + where + C: StreamCipher + KeyIvInit, + { + let (_, surb_ack_bytes) = self.surb_ack.prepare_for_sending(); + + let mut fragment_data = self.fragment.into_bytes(); + stream_cipher::encrypt_in_place::( + packet_encryption_key, + &stream_cipher::zero_iv::(), + &mut fragment_data, + ); + + // combines all the data as follows: + // SURB_ACK || VARIANT_SPECIFIC_DATA || CHUNK_DATA + // where variant-specific data is as follows: + // for replies it would be the digest of the encryption key used + // for 'regular' messages it would be the public component used in DH later used in the KDF + NymsphinxPayload( + surb_ack_bytes + .into_iter() + .chain(variant_data.into_iter()) + .chain(fragment_data.into_iter()) + .collect(), + ) + } + + pub fn build_reply(self, packet_encryption_key: &SurbEncryptionKey) -> NymsphinxPayload { + let key_digest = packet_encryption_key.compute_digest(); + self.build::( + packet_encryption_key.inner(), + key_digest.into_iter(), + ) + } + + pub fn build_regular( + self, + rng: &mut R, + recipient_encryption_key: &encryption::PublicKey, + ) -> NymsphinxPayload + where + R: RngCore + CryptoRng, + { + // create keys for 'payload' encryption + let (ephemeral_keypair, shared_key) = new_ephemeral_shared_key::< + PacketEncryptionAlgorithm, + PacketHkdfAlgorithm, + _, + >(rng, recipient_encryption_key); + + self.build::( + &shared_key, + ephemeral_keypair.public_key().to_bytes(), + ) + } +} + +// the actual byte data that will be put into the sphinx packet paylaod. +// no more transformations are going to happen to it +// TODO: use that fact for some better compile time assertions +pub struct NymsphinxPayload(Vec); + +impl AsRef<[u8]> for NymsphinxPayload { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index 0b6a8e96dd..b6a10ed2df 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -1,13 +1,22 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::message::{NymMessage, NymMessageError, PaddedMessage, PlainMessage}; +use crypto::aes::cipher::{KeyIvInit, StreamCipher}; use crypto::asymmetric::encryption; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; -use nymsphinx_anonymous_replies::reply_surb::{ReplySurb, ReplySurbError}; +use crypto::symmetric::stream_cipher::CipherKey; +use nymsphinx_anonymous_replies::requests::AnonymousSenderTag; +use nymsphinx_anonymous_replies::SurbEncryptionKey; use nymsphinx_chunking::fragment::Fragment; use nymsphinx_chunking::reconstruction::MessageReconstructor; -use nymsphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, DEFAULT_NUM_MIX_HOPS}; +use nymsphinx_chunking::ChunkingError; +use nymsphinx_params::{ + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, + DEFAULT_NUM_MIX_HOPS, +}; +use thiserror::Error; // TODO: should this live in this file? #[derive(Debug)] @@ -15,31 +24,43 @@ pub struct ReconstructedMessage { /// The actual plaintext message that was received. pub message: Vec, - /// Optional ReplySURB to allow for an anonymous reply to the sender. - pub reply_surb: Option, + /// Optional ephemeral sender tag indicating pseudo-identity of the party who sent us the message + /// (alongside any reply SURBs) + pub sender_tag: Option, } -#[derive(Debug)] -pub enum MessageRecoveryError { - InvalidSurbPrefixError, - MalformedSurbError(ReplySurbError), - InvalidRemoteEphemeralKey(encryption::KeyRecoveryError), - MalformedFragmentError, - InvalidMessagePaddingError, - MalformedReconstructedMessage(Vec), - TooShortMessageError, +impl ReconstructedMessage { + pub fn new(message: Vec, sender_tag: AnonymousSenderTag) -> Self { + Self { + message, + sender_tag: Some(sender_tag), + } + } } -impl From for MessageRecoveryError { - fn from(err: ReplySurbError) -> Self { - MessageRecoveryError::MalformedSurbError(err) +impl From for ReconstructedMessage { + fn from(message: PlainMessage) -> Self { + ReconstructedMessage { + message, + sender_tag: None, + } } } -impl From for MessageRecoveryError { - fn from(err: encryption::KeyRecoveryError) -> Self { - MessageRecoveryError::InvalidRemoteEphemeralKey(err) - } +#[derive(Debug, Error)] +pub enum MessageRecoveryError { + #[error("Recovered remote x25519 public key is invalid - {0}")] + InvalidRemoteEphemeralKey(#[from] encryption::KeyRecoveryError), + + #[error("The reconstructed message was malformed - {source}")] + MalformedReconstructedMessage { + #[source] + source: NymMessageError, + used_sets: Vec, + }, + + #[error("Failed to recover message fragment - {0}")] + FragmentRecoveryError(#[from] ChunkingError), } pub struct MessageReceiver { @@ -64,36 +85,34 @@ impl MessageReceiver { self } - /// Parses the message to strip and optionally recover reply SURB. - fn recover_reply_surb_from_message( - &self, - message: &mut Vec, - ) -> Result, MessageRecoveryError> { - match message[0] { - n if n == false as u8 => { - message.remove(0); - Ok(None) - } - n if n == true as u8 => { - let surb_len: usize = ReplySurb::serialized_len(self.num_mix_hops); - // note the extra +1 (due to 0/1 message prefix) - let surb_bytes = &message[1..1 + surb_len]; - let reply_surb = ReplySurb::from_bytes(surb_bytes)?; + fn decrypt_raw_message(&self, message: &mut [u8], key: &CipherKey) + where + C: StreamCipher + KeyIvInit, + { + let zero_iv = stream_cipher::zero_iv::(); + stream_cipher::decrypt_in_place::(key, &zero_iv, message) + } - *message = message.drain(1 + surb_len..).collect(); - Ok(Some(reply_surb)) - } - _ => Err(MessageRecoveryError::InvalidSurbPrefixError), - } + /// Given raw fragment data, **WITH KEY DIGEST PREFIX ALREADY REMOVED!!**, uses looked up + /// key to decrypt fragment data + pub fn recover_plaintext_from_reply( + &self, + reply_ciphertext: &mut [u8], + reply_key: SurbEncryptionKey, + ) { + self.decrypt_raw_message::( + reply_ciphertext, + reply_key.inner(), + ) } /// Given raw fragment data, recovers the remote ephemeral key, recomputes shared secret, /// uses it to decrypt fragment data - pub fn recover_plaintext( + pub fn recover_plaintext_from_regular_packet<'a>( &self, local_key: &encryption::PrivateKey, - mut raw_enc_frag: Vec, - ) -> Result, MessageRecoveryError> { + raw_enc_frag: &'a mut [u8], + ) -> Result<&'a mut [u8], MessageRecoveryError> { // 1. recover remote encryption key let remote_key_bytes = &raw_enc_frag[..encryption::PUBLIC_KEY_SIZE]; let remote_ephemeral_key = encryption::PublicKey::from_bytes(remote_key_bytes)?; @@ -105,33 +124,17 @@ impl MessageReceiver { ); // 3. decrypt fragment data - let fragment_bytes = &mut raw_enc_frag[encryption::PUBLIC_KEY_SIZE..]; + let fragment_ciphertext = &mut raw_enc_frag[encryption::PUBLIC_KEY_SIZE..]; + + self.decrypt_raw_message::(fragment_ciphertext, &encryption_key); + let fragment_data = fragment_ciphertext; - let zero_iv = stream_cipher::zero_iv::(); - Ok(stream_cipher::decrypt::( - &encryption_key, - &zero_iv, - fragment_bytes, - )) + Ok(fragment_data) } /// Given fragment data recovers [`Fragment`] itself. pub fn recover_fragment(&self, frag_data: &[u8]) -> Result { - Fragment::try_from_bytes(frag_data) - .map_err(|_| MessageRecoveryError::MalformedFragmentError) - } - - /// Removes the zero padding from the message that was initially included to ensure same length - /// sphinx payloads. - pub fn remove_padding(message: &mut Vec) -> Result<(), MessageRecoveryError> { - // we are looking for first occurrence of 1 in the tail and we get its index - if let Some(i) = message.iter().rposition(|b| *b == 1) { - // and now we only take bytes until that point (but not including it) - *message = message.drain(..i).collect(); - Ok(()) - } else { - Err(MessageRecoveryError::InvalidMessagePaddingError) - } + Ok(Fragment::try_from_bytes(frag_data)?) } /// Inserts given [`Fragment`] into the reconstructor. @@ -144,30 +147,15 @@ impl MessageReceiver { pub fn insert_new_fragment( &mut self, fragment: Fragment, - ) -> Result)>, MessageRecoveryError> { - if let Some((mut message, used_sets)) = self.reconstructor.insert_new_fragment(fragment) { - // Split message into plaintext and reply-SURB - let reply_surb = match self.recover_reply_surb_from_message(&mut message) { - Ok(reply_surb) => reply_surb, - Err(_) => { - return Err(MessageRecoveryError::MalformedReconstructedMessage( - used_sets, - )); - } - }; - - // Finally, remove the zero padding from the message - Self::remove_padding(&mut message).map_err(|_| { - MessageRecoveryError::MalformedReconstructedMessage(used_sets.clone()) - })?; - - Ok(Some(( - ReconstructedMessage { - message, - reply_surb, - }, - used_sets, - ))) + ) -> Result)>, MessageRecoveryError> { + if let Some((message, used_sets)) = self.reconstructor.insert_new_fragment(fragment) { + match PaddedMessage::new_reconstructed(message).remove_padding(self.num_mix_hops) { + Ok(message) => Ok(Some((message, used_sets))), + Err(err) => Err(MessageRecoveryError::MalformedReconstructedMessage { + source: err, + used_sets, + }), + } } else { Ok(None) } @@ -188,10 +176,7 @@ mod message_receiver { use super::*; use crypto::asymmetric::identity; use mixnet_contract_common::Layer; - use nymsphinx_addressing::clients::Recipient; - use rand::rngs::OsRng; use std::collections::HashMap; - use std::time::Duration; use topology::{gateway, mix, NymTopology}; // TODO: is it somehow maybe possible to move it to `topology` and have if conditionally @@ -282,41 +267,4 @@ mod message_receiver { }], ) } - - #[test] - fn correctly_splits_message_into_plaintext_and_surb() { - let message_receiver: MessageReceiver = Default::default(); - - // the actual 'correctness' of the underlying message doesn't matter for this test - let message = vec![42; 100]; - let dummy_recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML").unwrap(); - let average_delay = Duration::from_millis(500); - let topology = topology_fixture(); - - let reply_surb = - ReplySurb::construct(&mut OsRng, &dummy_recipient, average_delay, &topology).unwrap(); - - let reply_surb_bytes = reply_surb.to_bytes(); - - // this is not exactly what is 'received' but rather after "some" processing, however, - // this is the expected argument to the function - let mut received_without_surb: Vec<_> = - std::iter::once(0).chain(message.iter().cloned()).collect(); - - let reply_surb = message_receiver - .recover_reply_surb_from_message(&mut received_without_surb) - .unwrap(); - assert_eq!(received_without_surb, message); - assert!(reply_surb.is_none()); - - let mut received_with_surb: Vec<_> = std::iter::once(1) - .chain(reply_surb_bytes.iter().cloned()) - .chain(message.iter().cloned()) - .collect(); - let reply_surb = message_receiver - .recover_reply_surb_from_message(&mut received_with_surb) - .unwrap(); - assert_eq!(received_with_surb, message); - assert_eq!(reply_surb_bytes, reply_surb.unwrap().to_bytes()); - } } diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 34a916bbf5..353de2b1b2 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -sphinx = { git = "https://github.com/nymtech/sphinx", rev="c494250f2a78bed33a618d470792418eee932859" } +sphinx = { git = "https://github.com/nymtech/sphinx", rev="e05a1992522ed0afd3c6fcac160313ffc9bb306a" } #sphinx = { path = "../../../../sphinx"} diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index f6921e362a..3148f6cd84 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -15,6 +15,18 @@ pub enum RequestFlag { Send = 1, } +impl TryFrom for RequestFlag { + type Error = RequestError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect), + _ if value == (RequestFlag::Send as u8) => Ok(Self::Send), + _ => Err(RequestError::UnknownRequestFlag), + } + } +} + #[derive(Debug, Error)] pub enum RequestError { #[error("not enough bytes to recover the length of the address")] @@ -45,23 +57,12 @@ impl RequestError { } } -impl TryFrom for RequestFlag { - type Error = RequestError; - - fn try_from(value: u8) -> Result { - match value { - _ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect), - _ if value == (RequestFlag::Send as u8) => Ok(Self::Send), - _ => Err(RequestError::UnknownRequestFlag), - } - } -} - #[derive(Debug)] pub struct ConnectRequest { + // TODO: is connection_id redundant now? pub conn_id: ConnectionId, pub remote_addr: RemoteAddress, - pub return_address: Recipient, + pub return_address: Option, } /// A request from a SOCKS5 client that a Nym Socks5 service provider should @@ -82,7 +83,7 @@ impl Request { pub fn new_connect( conn_id: ConnectionId, remote_addr: RemoteAddress, - return_address: Recipient, + return_address: Option, ) -> Request { Request::Connect(Box::new(ConnectRequest { conn_id, @@ -99,12 +100,13 @@ impl Request { /// Deserialize the request type, connection id, destination address and port, /// and the request body from bytes. /// - /// Serialized bytes looks like this: - /// - /// -------------------------------------------------------------------------------------- - /// request_flag | connection_id | address_length | remote_address_bytes | request_data | - /// 1 | 8 | 2 | address_length | ... | - /// -------------------------------------------------------------------------------------- + // TODO: this was already inaccurate + // /// Serialized bytes looks like this: + // /// + // /// -------------------------------------------------------------------------------------- + // /// request_flag | connection_id | address_length | remote_address_bytes | request_data | + // /// 1 | 8 | 2 | address_length | ... | + // /// -------------------------------------------------------------------------------------- /// /// The request_flag tells us whether this is a new connection request (`new_connect`), /// an already-established connection we should send up (`new_send`), or @@ -144,14 +146,20 @@ impl Request { // just a temporary reference to mid-slice for ease of use let recipient_data_bytes = &connect_request_bytes[address_end..]; - if recipient_data_bytes.len() != Recipient::LEN { - return Err(RequestError::ReturnAddressTooShort); - } - - let mut return_bytes = [0u8; Recipient::LEN]; - return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]); - let return_address = Recipient::try_from_bytes(return_bytes) - .map_err(RequestError::MalformedReturnAddress)?; + let return_address = if recipient_data_bytes.is_empty() { + None + } else { + if recipient_data_bytes.len() != Recipient::LEN { + return Err(RequestError::ReturnAddressTooShort); + } + + let mut return_bytes = [0u8; Recipient::LEN]; + return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]); + Some( + Recipient::try_from_bytes(return_bytes) + .map_err(RequestError::MalformedReturnAddress)?, + ) + }; Ok(Request::new_connect( connection_id, @@ -161,7 +169,7 @@ impl Request { } RequestFlag::Send => { let local_closed = b[9] != 0; - let data = b[10..].as_ref().to_vec(); + let data = b[10..].to_vec(); Ok(Request::Send(connection_id, data, local_closed)) } @@ -178,15 +186,19 @@ impl Request { let remote_address_bytes = req.remote_addr.into_bytes(); let remote_address_bytes_len = remote_address_bytes.len() as u16; - std::iter::once(RequestFlag::Connect as u8) - .chain(req.conn_id.to_be_bytes().iter().cloned()) - .chain(remote_address_bytes_len.to_be_bytes().iter().cloned()) - .chain(remote_address_bytes.into_iter()) - .chain(req.return_address.to_bytes().iter().cloned()) - .collect() + let iter = std::iter::once(RequestFlag::Connect as u8) + .chain(req.conn_id.to_be_bytes().into_iter()) + .chain(remote_address_bytes_len.to_be_bytes().into_iter()) + .chain(remote_address_bytes.into_iter()); + + if let Some(return_address) = req.return_address { + iter.chain(return_address.to_bytes().into_iter()).collect() + } else { + iter.collect() + } } Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8) - .chain(conn_id.to_be_bytes().iter().cloned()) + .chain(conn_id.to_be_bytes().into_iter()) .chain(std::iter::once(local_closed as u8)) .chain(data.into_iter()) .collect(), @@ -324,7 +336,7 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes_prefix .iter() .cloned() - .chain(recipient_bytes.iter().cloned()) + .chain(recipient_bytes.into_iter()) .collect(); assert!(Request::try_from_bytes(&request_bytes) .unwrap_err() @@ -361,7 +373,7 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes .into_iter() - .chain(recipient_bytes.iter().cloned()) + .chain(recipient_bytes.into_iter()) .collect(); let request = Request::try_from_bytes(&request_bytes).unwrap(); @@ -370,7 +382,7 @@ mod request_deserialization_tests { assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!( - req.return_address.to_bytes().to_vec(), + req.return_address.unwrap().to_bytes().to_vec(), recipient.to_bytes().to_vec() ); } @@ -408,7 +420,7 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes .into_iter() - .chain(recipient_bytes.iter().cloned()) + .chain(recipient_bytes.into_iter()) .collect(); let request = Request::try_from_bytes(&request_bytes).unwrap(); @@ -417,7 +429,7 @@ mod request_deserialization_tests { assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!( - req.return_address.to_bytes().to_vec(), + req.return_address.unwrap().to_bytes().to_vec(), recipient.to_bytes().to_vec() ); } diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index f2a8a899fd..2996aeb639 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" bs58 = "0.4" log = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +thiserror = "1.0.37" ## internal crypto = { path = "../crypto" } diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index ed6b7c71fb..4178ad9a32 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::filter::VersionFilterable; @@ -14,19 +14,22 @@ use std::fmt::{self, Display, Formatter}; use std::io; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::str::FromStr; +use thiserror::Error; pub mod filter; pub mod gateway; pub mod mix; -#[derive(Debug)] +#[derive(Debug, Clone, Error)] pub enum NymTopologyError { - InvalidMixLayerError, - MissingLayerError(Vec), - NonExistentGatewayError, + #[error("Gateway with identity key {identity_key} doesn't exist")] + NonExistentGatewayError { identity_key: String }, - InvalidNumberOfHopsError, - NoMixesOnLayerAvailable(MixLayer), + #[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")] + InvalidNumberOfHopsError { available: usize, requested: usize }, + + #[error("No mixnodes available on layer {layer}")] + NoMixesOnLayerAvailable { layer: MixLayer }, } #[derive(Debug, Clone)] @@ -130,7 +133,10 @@ impl NymTopology { use rand::seq::SliceRandom; if self.mixes.len() < num_mix_hops as usize { - return Err(NymTopologyError::InvalidNumberOfHopsError); + return Err(NymTopologyError::InvalidNumberOfHopsError { + available: self.mixes.len(), + requested: num_mix_hops as usize, + }); } let mut route = Vec::with_capacity(num_mix_hops as usize); @@ -140,13 +146,13 @@ impl NymTopology { let layer_mixes = self .mixes .get(&layer) - .ok_or(NymTopologyError::NoMixesOnLayerAvailable(layer))?; + .ok_or(NymTopologyError::NoMixesOnLayerAvailable { layer })?; // choose a random mix from the above list // this can return a 'None' only if slice is empty let random_mix = layer_mixes .choose(rng) - .ok_or(NymTopologyError::NoMixesOnLayerAvailable(layer))?; + .ok_or(NymTopologyError::NoMixesOnLayerAvailable { layer })?; route.push(random_mix.into()); } @@ -165,9 +171,11 @@ impl NymTopology { // I don't think there's a need for this RNG to be crypto-secure R: Rng + ?Sized, { - let gateway = self - .get_gateway(gateway_identity) - .ok_or(NymTopologyError::NonExistentGatewayError)?; + let gateway = self.get_gateway(gateway_identity).ok_or( + NymTopologyError::NonExistentGatewayError { + identity_key: gateway_identity.to_base58_string(), + }, + )?; Ok(self .random_mix_route(rng, num_mix_hops)? diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index d2fc273d22..9720faee81 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1413,7 +1413,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859" +source = "git+https://github.com/nymtech/sphinx?rev=e05a1992522ed0afd3c6fcac160313ffc9bb306a#e05a1992522ed0afd3c6fcac160313ffc9bb306a" dependencies = [ "aes", "arrayref", diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs index 9be8175bed..81e0303afd 100644 --- a/mixnode/src/node/packet_delayforwarder.rs +++ b/mixnode/src/node/packet_delayforwarder.rs @@ -194,7 +194,7 @@ mod tests { ]; SphinxPacketBuilder::new() .with_payload_size(size.payload_size()) - .build_packet(b"foomp".to_vec(), &route, &destination, &delays) + .build_packet(b"foomp", &route, &destination, &delays) .unwrap() } diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index cab899eacf..16045a48d9 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -123,9 +123,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -635,6 +635,7 @@ dependencies = [ "client-connections", "config", "crypto", + "dashmap", "dirs", "futures", "gateway-client", @@ -1285,6 +1286,19 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.3", +] + [[package]] name = "deflate" version = "0.7.20" @@ -3424,6 +3438,7 @@ dependencies = [ "nymsphinx-types", "rand 0.7.3", "rand_distr", + "thiserror", "tokio", "topology", ] @@ -3448,6 +3463,7 @@ dependencies = [ "crypto", "nymsphinx-types", "serde", + "thiserror", ] [[package]] @@ -3461,6 +3477,7 @@ dependencies = [ "nymsphinx-types", "rand 0.7.3", "serde", + "thiserror", "topology", ] @@ -3473,6 +3490,7 @@ dependencies = [ "nymsphinx-params", "nymsphinx-types", "rand 0.7.3", + "thiserror", ] [[package]] @@ -5107,7 +5125,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859" +source = "git+https://github.com/nymtech/sphinx?rev=e05a1992522ed0afd3c6fcac160313ffc9bb306a#e05a1992522ed0afd3c6fcac160313ffc9bb306a" dependencies = [ "aes 0.7.5", "arrayref", @@ -5784,18 +5802,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -5951,6 +5969,7 @@ dependencies = [ "nymsphinx-addressing", "nymsphinx-types", "rand 0.7.3", + "thiserror", "version-checker", ] diff --git a/service-providers/network-requester/src/connection.rs b/service-providers/network-requester/src/connection.rs index 95bdac06a3..f4ffaca992 100644 --- a/service-providers/network-requester/src/connection.rs +++ b/service-providers/network-requester/src/connection.rs @@ -1,8 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::core::ReturnAddress; use client_connections::LaneQueueLengths; -use nymsphinx::addressing::clients::Recipient; use proxy_helpers::connection_controller::ConnectionReceiver; use proxy_helpers::proxy_runner::{MixProxySender, ProxyRunner}; use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Response}; @@ -18,14 +18,14 @@ pub(crate) struct Connection { id: ConnectionId, address: RemoteAddress, conn: Option, - return_address: Recipient, + return_address: ReturnAddress, } impl Connection { pub(crate) async fn new( id: ConnectionId, address: RemoteAddress, - return_address: Recipient, + return_address: ReturnAddress, ) -> io::Result { let conn = TcpStream::connect(&address).await?; @@ -40,14 +40,14 @@ impl Connection { pub(crate) async fn run_proxy( &mut self, mix_receiver: ConnectionReceiver, - mix_sender: MixProxySender<(Socks5Message, Recipient)>, + mix_sender: MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, shutdown: ShutdownListener, ) { let stream = self.conn.take().unwrap(); let remote_source_address = "???".to_string(); // we don't know ip address of requester let connection_id = self.id; - let recipient = self.return_address; + let return_address = self.return_address.clone(); let (stream, _) = ProxyRunner::new( stream, self.address.clone(), @@ -61,7 +61,7 @@ impl Connection { .run(move |conn_id, read_data, socket_closed| { ( Socks5Message::Response(Response::new(conn_id, read_data, socket_closed)), - recipient, + return_address.clone(), ) }) .await diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 870376d896..965e2e6b16 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -15,13 +15,15 @@ use futures::stream::{SplitSink, SplitStream}; use futures::{SinkExt, StreamExt}; use log::*; use nymsphinx::addressing::clients::Recipient; +use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::receiver::ReconstructedMessage; use proxy_helpers::connection_controller::{ BroadcastActiveConnections, Controller, ControllerCommand, ControllerSender, }; use proxy_helpers::proxy_runner::{MixProxyReader, MixProxySender}; use socks5_requests::{ - ConnectionId, Message as Socks5Message, NetworkRequesterResponse, Request, Response, + ConnectRequest, ConnectionId, Message as Socks5Message, NetworkRequesterResponse, Request, + Response, }; use statistics_common::collector::StatisticsSender; use std::path::PathBuf; @@ -41,6 +43,56 @@ pub struct ServiceProvider { stats_provider_addr: Option, } +// TODO: move elsewhere after things settle +#[derive(Debug, Clone)] +pub enum ReturnAddress { + Known(Box), + Anonymous(AnonymousSenderTag), +} + +impl From for ReturnAddress { + fn from(recipient: Recipient) -> Self { + ReturnAddress::Known(Box::new(recipient)) + } +} + +impl From for ReturnAddress { + fn from(sender_tag: AnonymousSenderTag) -> Self { + ReturnAddress::Anonymous(sender_tag) + } +} + +impl ReturnAddress { + fn new( + explicit_return_address: Option, + implicit_tag: Option, + ) -> Option { + // if somehow we received both, always prefer the explicit address since it's way easier to use + if let Some(recipient) = explicit_return_address { + return Some(ReturnAddress::Known(Box::new(recipient))); + } + if let Some(sender_tag) = implicit_tag { + return Some(ReturnAddress::Anonymous(sender_tag)); + } + None + } + + fn send_back_to(self, message: Vec, connection_id: u64) -> ClientRequest { + match self { + ReturnAddress::Known(recipient) => ClientRequest::Send { + recipient: *recipient, + message, + connection_id: Some(connection_id), + }, + ReturnAddress::Anonymous(sender_tag) => ClientRequest::Reply { + message, + sender_tag, + connection_id: Some(connection_id), + }, + } + } +} + impl ServiceProvider { pub fn new( listening_address: String, @@ -72,13 +124,12 @@ impl ServiceProvider { /// via the `websocket_writer`. async fn mixnet_response_listener( mut websocket_writer: SplitSink, - mut mix_reader: MixProxyReader<(Socks5Message, Recipient)>, + mut mix_reader: MixProxyReader<(Socks5Message, ReturnAddress)>, stats_collector: Option, mut client_connection_rx: ConnectionCommandReceiver, ) { loop { tokio::select! { - // TODO: wire SURBs in here once they're available socks5_msg = mix_reader.recv() => { if let Some((msg, return_address)) = socks5_msg { if let Some(stats_collector) = stats_collector.as_ref() { @@ -95,15 +146,10 @@ impl ServiceProvider { .processed(remote_addr, msg.size() as u32); } } - let conn_id = msg.conn_id(); // make 'request' to native-websocket client - let response_message = ClientRequest::Send { - recipient: return_address, - message: msg.into_bytes(), - with_reply_surb: false, - connection_id: Some(conn_id), - }; + let conn_id = msg.conn_id(); + let response_message = return_address.send_back_to(msg.into_bytes(), conn_id); let message = Message::Binary(response_message.serialize()); websocket_writer.send(message).await.unwrap(); @@ -175,7 +221,7 @@ impl ServiceProvider { let received = match deserialized_message { ServerResponse::Received(received) => received, - ServerResponse::LaneQueueLength(lane, queue_length) => { + ServerResponse::LaneQueueLength { lane, queue_length } => { Self::handle_lane_queue_length_response( &lane_queue_lengths, lane, @@ -196,33 +242,34 @@ impl ServiceProvider { async fn start_proxy( conn_id: ConnectionId, remote_addr: String, - return_address: Recipient, + return_address: ReturnAddress, controller_sender: ControllerSender, - mix_input_sender: MixProxySender<(Socks5Message, Recipient)>, + mix_input_sender: MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, shutdown: ShutdownListener, ) { - let mut conn = match Connection::new(conn_id, remote_addr.clone(), return_address).await { - Ok(conn) => conn, - Err(err) => { - error!( - "error while connecting to {:?} ! - {:?}", - remote_addr.clone(), - err - ); + let mut conn = + match Connection::new(conn_id, remote_addr.clone(), return_address.clone()).await { + Ok(conn) => conn, + Err(err) => { + error!( + "error while connecting to {:?} ! - {:?}", + remote_addr.clone(), + err + ); - // inform the remote that the connection is closed before it even was established - mix_input_sender - .send(( - Socks5Message::Response(Response::new(conn_id, Vec::new(), true)), - return_address, - )) - .await - .expect("InputMessageReceiver has stopped receiving!"); + // inform the remote that the connection is closed before it even was established + mix_input_sender + .send(( + Socks5Message::Response(Response::new(conn_id, Vec::new(), true)), + return_address, + )) + .await + .expect("InputMessageReceiver has stopped receiving!"); - return; - } - }; + return; + } + }; // Connect implies it's a fresh connection - register it with our controller let (mix_sender, mix_receiver) = mpsc::unbounded(); @@ -258,13 +305,25 @@ impl ServiceProvider { async fn handle_proxy_connect( &mut self, controller_sender: &mut ControllerSender, - mix_input_sender: &MixProxySender<(Socks5Message, Recipient)>, + mix_input_sender: &MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, - conn_id: ConnectionId, - remote_addr: String, - return_address: Recipient, + sender_tag: Option, + connect_req: Box, shutdown: ShutdownListener, ) { + let return_address = match ReturnAddress::new(connect_req.return_address, sender_tag) { + Some(address) => address, + None => { + log::warn!( + "attempted to start connection with no way of returning data back to the sender" + ); + return; + } + }; + + let remote_addr = connect_req.remote_addr; + let conn_id = connect_req.conn_id; + if !self.open_proxy && !self.outbound_request_filter.check(&remote_addr) { let log_msg = format!("Domain {:?} failed filter check", remote_addr); log::info!("{}", log_msg); @@ -311,23 +370,24 @@ impl ServiceProvider { async fn handle_proxy_message( &mut self, - raw_request: &[u8], + message: ReconstructedMessage, controller_sender: &mut ControllerSender, - mix_input_sender: &MixProxySender<(Socks5Message, Recipient)>, + mix_input_sender: &MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, stats_collector: Option, shutdown: ShutdownListener, ) { - let deserialized_msg = match Socks5Message::try_from_bytes(raw_request) { + let deserialized_msg = match Socks5Message::try_from_bytes(&message.message) { Ok(msg) => msg, Err(err) => { - error!("Failed to deserialized received message! - {}", err); + error!("Failed to deserialized received message! - {err}"); return; } }; match deserialized_msg { Socks5Message::Request(deserialized_request) => match deserialized_request { Request::Connect(req) => { + // TODO: stats might be invalid if connection fails to start if let Some(stats_collector) = stats_collector { stats_collector .connected_services @@ -339,9 +399,8 @@ impl ServiceProvider { controller_sender, mix_input_sender, lane_queue_lengths, - req.conn_id, - req.remote_addr, - req.return_address, + message.sender_tag, + req, shutdown, ) .await @@ -379,7 +438,7 @@ impl ServiceProvider { // channels responsible for managing messages that are to be sent to the mix network. The receiver is // going to be used by `mixnet_response_listener` let (mix_input_sender, mix_input_receiver) = - tokio::sync::mpsc::channel::<(Socks5Message, Recipient)>(1); + tokio::sync::mpsc::channel::<(Socks5Message, ReturnAddress)>(1); // Used to notify tasks to shutdown. Not all tasks fully supports this (yet). let shutdown = task::ShutdownNotifier::default(); @@ -446,11 +505,8 @@ impl ServiceProvider { return Ok(()); }; - let raw_message = received.message; - // TODO: here be potential SURB (i.e. received.reply_SURB) - self.handle_proxy_message( - &raw_message, + received, &mut controller_sender, &mix_input_sender, shared_lane_queue_lengths.clone(), diff --git a/service-providers/network-requester/src/statistics/collector.rs b/service-providers/network-requester/src/statistics/collector.rs index ac43252c33..73e6918ea5 100644 --- a/service-providers/network-requester/src/statistics/collector.rs +++ b/service-providers/network-requester/src/statistics/collector.rs @@ -12,6 +12,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; +use crate::core::ReturnAddress; use nymsphinx::addressing::clients::Recipient; use ordered_buffer::OrderedMessageSender; use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Request}; @@ -77,13 +78,13 @@ pub struct ServiceStatisticsCollector { pub(crate) response_stats_data: Arc>, pub(crate) connected_services: Arc>>, stats_provider_addr: Recipient, - mix_input_sender: MixProxySender<(Socks5Message, Recipient)>, + mix_input_sender: MixProxySender<(Socks5Message, ReturnAddress)>, } impl ServiceStatisticsCollector { pub async fn new( stats_provider_addr: Option, - mix_input_sender: MixProxySender<(Socks5Message, Recipient)>, + mix_input_sender: MixProxySender<(Socks5Message, ReturnAddress)>, ) -> Result { let client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) @@ -173,12 +174,12 @@ impl StatisticsCollector for ServiceStatisticsCollector { "{}:{}", DEFAULT_STATISTICS_SERVICE_ADDRESS, DEFAULT_STATISTICS_SERVICE_PORT ), - self.stats_provider_addr, + Some(self.stats_provider_addr), ); self.mix_input_sender .send(( Socks5Message::Request(connect_req), - self.stats_provider_addr, + self.stats_provider_addr.into(), )) .await .expect("MixProxyReader has stopped receiving!"); @@ -188,7 +189,10 @@ impl StatisticsCollector for ServiceStatisticsCollector { let ordered_msg = message_sender.wrap_message(msg).into_bytes(); let send_req = Request::new_send(conn_id, ordered_msg, true); self.mix_input_sender - .send((Socks5Message::Request(send_req), self.stats_provider_addr)) + .send(( + Socks5Message::Request(send_req), + self.stats_provider_addr.into(), + )) .await .expect("MixProxyReader has stopped receiving!"); diff --git a/validator-api/src/network_monitor/chunker.rs b/validator-api/src/network_monitor/chunker.rs index ec0da08871..0caaba0811 100644 --- a/validator-api/src/network_monitor/chunker.rs +++ b/validator-api/src/network_monitor/chunker.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::message::NymMessage; use nymsphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer, }; @@ -52,10 +53,9 @@ impl Chunker { ) -> Vec { let ack_key: AckKey = AckKey::new(&mut self.rng); - let (split_message, _reply_keys) = self + let split_message = self .message_preparer - .prepare_and_split_message(message, false, topology) - .expect("failed to split the message"); + .pad_and_split_message(NymMessage::new_plain(message)); let mut mix_packets = Vec::with_capacity(split_message.len()); for message_chunk in split_message { diff --git a/validator-api/src/network_monitor/monitor/processor.rs b/validator-api/src/network_monitor/monitor/processor.rs index 28ef3ae900..7b0649a49d 100644 --- a/validator-api/src/network_monitor/monitor/processor.rs +++ b/validator-api/src/network_monitor/monitor/processor.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::network_monitor::gateways_reader::GatewayMessages; @@ -68,27 +68,30 @@ struct ReceivedProcessorInner { } impl ReceivedProcessorInner { - fn on_message(&mut self, message: Vec) -> Result<(), ProcessingError> { + fn on_message(&mut self, mut message: Vec) -> Result<(), ProcessingError> { // if the nonce is none it means the packet was received during the 'waiting' for the // next test run if self.test_nonce.is_none() { return Err(ProcessingError::ReceivedOutsideTestRun); } - let encrypted_bytes = self + let plaintext = self .message_receiver - .recover_plaintext(self.client_encryption_keypair.private_key(), message) + .recover_plaintext_from_regular_packet( + self.client_encryption_keypair.private_key(), + &mut message, + ) .map_err(|_| ProcessingError::MalformedPacketReceived)?; let fragment = self .message_receiver - .recover_fragment(&encrypted_bytes) + .recover_fragment(plaintext) .map_err(|_| ProcessingError::MalformedPacketReceived)?; let (recovered, _) = self .message_receiver .insert_new_fragment(fragment) .map_err(|_| ProcessingError::MalformedPacketReceived)? .ok_or(ProcessingError::NonTestPacketReceived)?; // if it's a test packet it MUST BE reconstructed with single fragment - let test_packet = TestPacket::try_from_bytes(&recovered.message) + let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data()) .map_err(|_| ProcessingError::MalformedPacketReceived)?; // we know nonce is NOT none