Feature/packet retransmission (#263)
* Ability to send sphinx packets of different sizes + more efficient decoding * Closing connection on connection corruption * Missing semicolons * Missing license notices * Default for packetsize * Split nymsphinx * Replaced Mutex with RwLock for TopologyAccessor + impl Deref * Sphinx update + import cleanup * Moved packet_sizes file * Updated NymTopology API * sphinx version bump * Missing license notice * nymsphinx-params crate * Changes due to ibid. * Chunking rework to allow variable size fragments * Initial ack crate * Version bump to new dev build * Cargo lock changes * random_route_to_gateway by node address * exposing getting read permit * Very initial draft on ack control * Correctly dereferencing out of topology read permit * All pending changes + compilation todo!s * Restricted scope of deref on TopologyAccessorInner * Type path alias for generate_key * Derived traits for MessageChunker * Ack control starting to take shape! * Awaiting callbacks * Most of work done on acks. Now to wire it all together * Import cleanup * rng generalization * Connected real traffic together; only acks from gateway left * Removed redundant things from nymsphinx::utils * nymsphinx-cover crate * Ack-related fields in client config * Decreased packet store log level * Restored forward sphinx request * Slight adjustements to surb acks * Changed TopologyReadPermit from type alias into a struct * Changes due to ibid. * Sphinx version upgrade * Gateway being able to understand and handle acks * Special Cover FragmentIdentifier + removal of dead code * Initial packet router for gateway client * Kill client if it fails to send to gateway too many times * Cover messages with acks * Moved out gateway client errors * Ignoring cover traffic acks * Changes in ack control * Another sphinx version upgrade * websocket handler delegating message chunking * Using config defined ack wait additions * Other minor changes I should have been more dilligent with splitting * Import path fix * sphinx_receiver => mixnet_receiver * Missing renamed variable instance * Updated aes-ctr to 0.4.0 * Removed concept of 'unfragmented' single fragment * Replay fragments detection * Long method split * typo * Cleaner client init * Fixed race condition * Fixed similar issue for retransmission * Cargo fmt * Minor clenaup
This commit is contained in:
committed by
GitHub
parent
db5540677c
commit
b57a548350
Generated
+166
-21
@@ -29,10 +29,22 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee"
|
||||
dependencies = [
|
||||
"aes-soft",
|
||||
"aesni",
|
||||
"ctr",
|
||||
"stream-cipher",
|
||||
"aes-soft 0.3.3",
|
||||
"aesni 0.6.0",
|
||||
"ctr 0.3.2",
|
||||
"stream-cipher 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-ctr"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e60aeefd2a0243bd53a42e92444e039f67c3d7f0382c9813577696e7c10bf3"
|
||||
dependencies = [
|
||||
"aes-soft 0.4.0",
|
||||
"aesni 0.7.0",
|
||||
"ctr 0.4.0",
|
||||
"stream-cipher 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -46,6 +58,17 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"byteorder",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.6.0"
|
||||
@@ -54,7 +77,18 @@ checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
|
||||
dependencies = [
|
||||
"block-cipher-trait",
|
||||
"opaque-debug",
|
||||
"stream-cipher",
|
||||
"stream-cipher 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"opaque-debug",
|
||||
"stream-cipher 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -212,7 +246,16 @@ dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-cipher"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10"
|
||||
dependencies = [
|
||||
"generic-array 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -221,7 +264,7 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -514,7 +557,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
"subtle 1.0.0",
|
||||
]
|
||||
|
||||
@@ -525,7 +568,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736"
|
||||
dependencies = [
|
||||
"block-cipher-trait",
|
||||
"stream-cipher",
|
||||
"stream-cipher 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3592740fd55aaf61dd72df96756bd0d11e6037b89dcf30ae2e1895b267692be"
|
||||
dependencies = [
|
||||
"stream-cipher 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -575,7 +627,7 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -929,6 +981,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2664c2cf08049036f31015b04c6ac3671379a1d86f52ed2416893f16022deb"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
@@ -1667,7 +1728,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"built",
|
||||
@@ -1687,6 +1748,7 @@ dependencies = [
|
||||
"pem",
|
||||
"pemstore",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"reqwest 0.9.24",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1717,7 +1779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"built",
|
||||
"clap",
|
||||
@@ -1747,7 +1809,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"built",
|
||||
@@ -1773,7 +1835,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"abci",
|
||||
"bodyparser",
|
||||
@@ -1803,14 +1865,86 @@ dependencies = [
|
||||
name = "nymsphinx"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"log 0.4.8",
|
||||
"nymsphinx-acknowledgements",
|
||||
"nymsphinx-addressing",
|
||||
"nymsphinx-chunking",
|
||||
"nymsphinx-cover",
|
||||
"nymsphinx-framing",
|
||||
"nymsphinx-params",
|
||||
"nymsphinx-types",
|
||||
"rand 0.7.3",
|
||||
"rand_distr",
|
||||
"sphinx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-acknowledgements"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-ctr 0.4.0",
|
||||
"nymsphinx-addressing",
|
||||
"nymsphinx-params",
|
||||
"nymsphinx-types",
|
||||
"rand 0.7.3",
|
||||
"topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-addressing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nymsphinx-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-chunking"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log 0.4.8",
|
||||
"nymsphinx-acknowledgements",
|
||||
"nymsphinx-addressing",
|
||||
"nymsphinx-params",
|
||||
"nymsphinx-types",
|
||||
"rand 0.7.3",
|
||||
"topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-cover"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nymsphinx-acknowledgements",
|
||||
"nymsphinx-addressing",
|
||||
"nymsphinx-chunking",
|
||||
"nymsphinx-params",
|
||||
"nymsphinx-types",
|
||||
"rand 0.7.3",
|
||||
"topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-framing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes 0.5.4",
|
||||
"nymsphinx-params",
|
||||
"nymsphinx-types",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-params"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nymsphinx-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nymsphinx-types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sphinx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
@@ -2755,9 +2889,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/nymtech/sphinx?rev=fcd17932acbd21a76c42a4bf2a42b9f8668f7e1f#fcd17932acbd21a76c42a4bf2a42b9f8668f7e1f"
|
||||
source = "git+https://github.com/nymtech/sphinx?rev=cd9f09b85de33c60030ba6d7fae613c42cbe1faf#cd9f09b85de33c60030ba6d7fae613c42cbe1faf"
|
||||
dependencies = [
|
||||
"aes-ctr",
|
||||
"aes-ctr 0.3.0",
|
||||
"arrayref",
|
||||
"blake2",
|
||||
"bs58",
|
||||
@@ -2780,7 +2914,17 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stream-cipher"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f8ed9974042b8c3672ff3030a69fcc03b74c47c3d1ecb7755e8a3626011e88"
|
||||
dependencies = [
|
||||
"block-cipher",
|
||||
"generic-array 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3130,7 +3274,8 @@ dependencies = [
|
||||
"futures 0.3.4",
|
||||
"itertools",
|
||||
"log 0.4.8",
|
||||
"nymsphinx",
|
||||
"nymsphinx-addressing",
|
||||
"nymsphinx-types",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
|
||||
@@ -15,6 +15,13 @@ members = [
|
||||
"common/crypto",
|
||||
"common/healthcheck",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
"common/nymsphinx/chunking",
|
||||
"common/nymsphinx/cover",
|
||||
"common/nymsphinx/framing",
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/types",
|
||||
"common/pemstore",
|
||||
"common/topology",
|
||||
"gateway",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-client"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -21,6 +21,7 @@ futures = "0.3.1"
|
||||
log = "0.4"
|
||||
pem = "0.7.0"
|
||||
pretty_env_logger = "0.3"
|
||||
rand = {version = "0.7.3", features = ["wasm-bindgen"]}
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
|
||||
@@ -17,25 +17,58 @@ use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::identifier::AckAes128Key;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::utils::{encapsulation, poisson};
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) struct LoopCoverTrafficStream<T: NymTopology> {
|
||||
average_packet_delay: Duration,
|
||||
average_cover_message_sending_delay: Duration,
|
||||
pub(crate) struct LoopCoverTrafficStream<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: time::Duration,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: time::Duration,
|
||||
|
||||
/// Average delay between sending subsequent cover packets.
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: time::Delay,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: MixMessageSender,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Stream for LoopCoverTrafficStream<T> {
|
||||
impl<R, T> Stream for LoopCoverTrafficStream<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
T: NymTopology, // this really confuses me, why T doesn't need to be Unpin?
|
||||
{
|
||||
// Item is only used to indicate we should create a new message rather than actual cover message
|
||||
// reason being to not introduce unnecessary complexity by having to keep state of topology
|
||||
// mutex when trying to acquire it. So right now the Stream trait serves as a glorified timer.
|
||||
@@ -50,8 +83,9 @@ impl<T: NymTopology> Stream for LoopCoverTrafficStream<T> {
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.average_cover_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = poisson::sample(self.average_cover_message_sending_delay);
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
@@ -62,52 +96,58 @@ impl<T: NymTopology> Stream for LoopCoverTrafficStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + NymTopology> LoopCoverTrafficStream<T> {
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl<T: 'static + NymTopology> LoopCoverTrafficStream<OsRng, T> {
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
average_ack_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
mix_tx: MixMessageSender,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
LoopCoverTrafficStream {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_cover_message_sending_delay,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_new_message(&mut self) {
|
||||
trace!("next cover message!");
|
||||
let route = match self
|
||||
.topology_access
|
||||
.random_route_to_gateway(&self.our_full_destination.gateway())
|
||||
.await
|
||||
{
|
||||
None => {
|
||||
warn!("No valid topology detected - won't send any loop cover message this time");
|
||||
return;
|
||||
}
|
||||
Some(route) => route,
|
||||
};
|
||||
|
||||
let cover_message = match encapsulation::loop_cover_message_route(
|
||||
self.our_full_destination.destination().clone(),
|
||||
route,
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
// 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(&self.our_full_destination, &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();
|
||||
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&*self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.average_packet_delay,
|
||||
) {
|
||||
Ok(message) => message,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Somehow we managed to create an invalid cover message - {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
@@ -116,6 +156,12 @@ impl<T: 'static + NymTopology> LoopCoverTrafficStream<T> {
|
||||
self.mix_tx
|
||||
.unbounded_send(MixMessage::new(cover_message.0, cover_message.1))
|
||||
.unwrap();
|
||||
|
||||
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
|
||||
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
|
||||
// properly blocks. So to play it on the safe side, just explicitly drop the read permit
|
||||
drop(topology_permit);
|
||||
|
||||
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
|
||||
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
|
||||
tokio::task::yield_now().await;
|
||||
@@ -123,8 +169,10 @@ impl<T: 'static + NymTopology> LoopCoverTrafficStream<T> {
|
||||
|
||||
async fn run(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay =
|
||||
time::delay_for(poisson::sample(self.average_cover_message_sending_delay));
|
||||
self.next_delay = time::delay_for(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.average_cover_message_sending_delay,
|
||||
));
|
||||
|
||||
while let Some(_) = self.next().await {
|
||||
self.on_new_message().await;
|
||||
|
||||
@@ -31,11 +31,17 @@ impl MixMessage {
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
pub(crate) struct MixTrafficController<'a> {
|
||||
// TODO: most likely to be replaced by some higher level construct as
|
||||
// later on gateway_client will need to be accessible by other entities
|
||||
gateway_client: GatewayClient<'a, url::Url>,
|
||||
mix_rx: MixMessageReceiver,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
}
|
||||
|
||||
impl<'a> MixTrafficController<'static> {
|
||||
@@ -46,6 +52,7 @@ impl<'a> MixTrafficController<'static> {
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +63,18 @@ impl<'a> MixTrafficController<'static> {
|
||||
.send_sphinx_packet(mix_message.0, mix_message.1)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Failed to send sphinx packet to the gateway! - {:?}", e),
|
||||
Ok(_) => trace!("We *might* have managed to forward sphinx packet to the gateway!"),
|
||||
Err(e) => {
|
||||
error!("Failed to send sphinx packet to the gateway! - {:?}", e);
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// todo: in the future this should initiate a 'graceful' shutdown
|
||||
panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT)
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
trace!("We *might* have managed to forward sphinx packet to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::mix_traffic::{MixMessageReceiver, MixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
|
||||
};
|
||||
@@ -26,20 +27,24 @@ use crate::websocket;
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use directory_client::presence;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender};
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use gateway_requests::auth_token::AuthToken;
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::identifier::AckAes128Key;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::split_and_prepare_payloads;
|
||||
use nymsphinx::NodeAddressBytes;
|
||||
use received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use topology::NymTopology;
|
||||
|
||||
mod cover_traffic_stream;
|
||||
pub(crate) mod inbound_messages;
|
||||
mod mix_traffic;
|
||||
mod real_traffic_stream;
|
||||
pub(crate) mod real_messages_control;
|
||||
pub(crate) mod received_buffer;
|
||||
pub(crate) mod topology_control;
|
||||
|
||||
@@ -79,6 +84,7 @@ impl NymClient {
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream<T: 'static + NymTopology>(
|
||||
&self,
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
mix_tx: MixMessageSender,
|
||||
) {
|
||||
@@ -88,37 +94,54 @@ impl NymClient {
|
||||
self.runtime
|
||||
.enter(|| {
|
||||
LoopCoverTrafficStream::new(
|
||||
ack_key,
|
||||
self.config.get_average_ack_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
self.config.get_loop_cover_traffic_average_delay(),
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
self.config.get_loop_cover_traffic_average_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
)
|
||||
})
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_stream<T: 'static + NymTopology>(
|
||||
// TODO: I'm not a fan of this function signature, i.e. that it returns the ACK key, but then again
|
||||
// I think the ACK key should be generated by 'acknowledgement_control' rather than by
|
||||
// the client itself. However, it all might change once we have to implement key rotation.
|
||||
fn start_real_traffic_controller<T: 'static + NymTopology>(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor<T>,
|
||||
mix_tx: MixMessageSender,
|
||||
input_rx: InputMessageReceiver,
|
||||
) {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: MixMessageSender,
|
||||
) -> Arc<AckAes128Key> {
|
||||
let controller_config = real_messages_control::Config::new(
|
||||
self.config.get_ack_wait_multiplier(),
|
||||
self.config.get_ack_wait_addition(),
|
||||
self.config.get_average_ack_delay(),
|
||||
self.config.get_message_sending_average_delay(),
|
||||
self.config.get_average_packet_delay(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
|
||||
// set in the constructor which HAS TO be called within context of a tokio runtime
|
||||
self.runtime
|
||||
.enter(|| {
|
||||
real_traffic_stream::OutQueueControl::new(
|
||||
mix_tx,
|
||||
input_rx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
self.config.get_average_packet_delay(),
|
||||
self.config.get_message_sending_average_delay(),
|
||||
)
|
||||
})
|
||||
.start(self.runtime.handle());
|
||||
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
|
||||
// When refactoring this restriction should definitely be removed.
|
||||
let real_messages_controller = self.runtime.enter(|| {
|
||||
RealMessagesController::new(
|
||||
controller_config,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
)
|
||||
});
|
||||
let ack_key = real_messages_controller.ack_key();
|
||||
real_messages_controller.start(self.runtime.handle());
|
||||
|
||||
ack_key
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -126,16 +149,17 @@ impl NymClient {
|
||||
fn start_received_messages_buffer_controller(
|
||||
&self,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
sphinx_receiver: SphinxPacketReceiver,
|
||||
mixnet_receiver: MixnetMessageReceiver,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(query_receiver, sphinx_receiver)
|
||||
ReceivedMessagesBufferController::new(query_receiver, mixnet_receiver)
|
||||
.start(self.runtime.handle())
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
sphinx_packet_sender: SphinxPacketSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
gateway_address: url::Url,
|
||||
) -> GatewayClient<'static, url::Url> {
|
||||
let auth_token = self
|
||||
@@ -148,7 +172,8 @@ impl NymClient {
|
||||
gateway_address,
|
||||
self.as_mix_recipient().destination(),
|
||||
auth_token,
|
||||
sphinx_packet_sender,
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
@@ -265,19 +290,13 @@ impl NymClient {
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>) {
|
||||
let split_message = split_and_prepare_payloads(&message);
|
||||
debug!(
|
||||
"Splitting message into {:?} fragments!",
|
||||
split_message.len()
|
||||
);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap()
|
||||
}
|
||||
let input_msg = InputMessage::new(recipient, message);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
@@ -323,9 +342,9 @@ impl NymClient {
|
||||
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
|
||||
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of [unwrapped] sphinx messages received from the gateway
|
||||
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (unwrapped_sphinx_sender, unwrapped_sphinx_receiver) = mpsc::unbounded();
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
|
||||
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
@@ -333,6 +352,9 @@ impl NymClient {
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
// TODO: when we switch to our graph topology, we need to remember to change 'presence::Topology' type
|
||||
let shared_topology_accessor = TopologyAccessor::<presence::Topology>::new();
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
@@ -340,24 +362,29 @@ impl NymClient {
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
unwrapped_sphinx_receiver,
|
||||
mixnet_messages_receiver,
|
||||
);
|
||||
|
||||
let gateway_url = self.runtime.block_on(Self::get_gateway_address(
|
||||
self.config.get_gateway_id(),
|
||||
shared_topology_accessor.clone(),
|
||||
));
|
||||
let gateway_client = self.start_gateway_client(unwrapped_sphinx_sender, gateway_url);
|
||||
let gateway_client =
|
||||
self.start_gateway_client(mixnet_messages_sender, ack_sender, gateway_url);
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_cover_traffic_stream(
|
||||
|
||||
let ack_key = self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
);
|
||||
self.start_real_traffic_stream(
|
||||
|
||||
self.start_cover_traffic_stream(
|
||||
ack_key,
|
||||
shared_topology_accessor.clone(),
|
||||
sphinx_message_sender,
|
||||
input_receiver,
|
||||
);
|
||||
|
||||
match self.config.get_socket_type() {
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::PendingAcksMap;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckAes128Key},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
// responsible for cancelling retransmission timers and removed entries from the map
|
||||
pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
pending_acks,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
debug!("Received an ack");
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content) {
|
||||
None => {
|
||||
warn!("Received invalid ACK!"); // should we do anything else about that?
|
||||
return;
|
||||
}
|
||||
Some(frag_id_bytes) => match FragmentIdentifier::try_from_bytes(&frag_id_bytes) {
|
||||
Ok(frag_id) => frag_id,
|
||||
Err(err) => {
|
||||
warn!("Received invalid ACK! - {:?}", err); // should we do anything else about that?
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(pending_ack) = self.pending_acks.write().await.remove(&frag_id) {
|
||||
// cancel the retransmission future
|
||||
pending_ack.retransmission_cancel.notify();
|
||||
} else {
|
||||
warn!("received ACK for packet we haven't stored! - {:?}", frag_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started AcknowledgementListener");
|
||||
while let Some(acks) = self.ack_receiver.next().await {
|
||||
// realistically we would only be getting one ack at the time, but if we managed to
|
||||
// introduce batching in gateway client, this call should be improved to not re-acquire
|
||||
// write permit on the map every loop iteration
|
||||
for ack in acks {
|
||||
self.on_ack(ack).await;
|
||||
}
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcknowledgement, PendingAcksMap};
|
||||
use crate::client::{
|
||||
inbound_messages::{InputMessage, InputMessageReceiver},
|
||||
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckAes128Key, addressing::clients::Recipient, chunking::MessageChunker,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use topology::NymTopology;
|
||||
|
||||
// responsible for splitting received message and initial sending attempt
|
||||
pub(super) struct InputMessageListener<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_chunker: MessageChunker<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
impl<R, T> InputMessageListener<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_chunker: MessageChunker<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
input_receiver,
|
||||
message_chunker,
|
||||
pending_acks,
|
||||
real_message_sender,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
let (recipient, content) = msg.destruct();
|
||||
let split_message = self.message_chunker.split_message(&content);
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
|
||||
let topology_ref_option =
|
||||
topology_permit.try_get_valid_topology_ref(&self.ack_recipient, &recipient);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("Could not process the message - the network topology is invalid");
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
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 {
|
||||
// since the paths can be constructed, this CAN'T fail, if it does, there's a bug somewhere
|
||||
let frag_id = message_chunk.fragment_identifier();
|
||||
// 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 (total_delay, (first_hop, packet)) = self
|
||||
.message_chunker
|
||||
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, &recipient)
|
||||
.unwrap();
|
||||
|
||||
real_messages.push(RealMessage::new(first_hop, packet, frag_id));
|
||||
|
||||
let pending_ack =
|
||||
PendingAcknowledgement::new(message_chunk, total_delay, recipient.clone());
|
||||
|
||||
pending_acks.push((frag_id, pending_ack));
|
||||
}
|
||||
|
||||
// first insert pending_acks only then request fragments to be sent, otherwise you might get
|
||||
// some very nasty (and time-consuming to figure out...) race condition.
|
||||
let mut pending_acks_map_write_guard = self.pending_acks.write().await;
|
||||
for (frag_id, pending_ack) in pending_acks.into_iter() {
|
||||
if let Some(_) = pending_acks_map_write_guard.insert(frag_id, pending_ack) {
|
||||
panic!("Tried to insert duplicate pending ack")
|
||||
}
|
||||
}
|
||||
|
||||
for real_message in real_messages {
|
||||
self.real_message_sender
|
||||
.unbounded_send(real_message)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started InputMessageListener");
|
||||
while let Some(input_msg) = self.input_receiver.next().await {
|
||||
self.on_input_message(input_msg).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use self::{
|
||||
acknowledgement_listener::AcknowledgementListener,
|
||||
input_message_listener::InputMessageListener,
|
||||
retransmission_request_listener::RetransmissionRequestListener,
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use super::real_traffic_stream::RealMessageSender;
|
||||
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::{self, identifier::AckAes128Key},
|
||||
addressing::clients::Recipient,
|
||||
chunking::{
|
||||
fragment::{Fragment, FragmentIdentifier},
|
||||
MessageChunker,
|
||||
},
|
||||
Delay,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use tokio::{
|
||||
sync::{Notify, RwLock},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use topology::NymTopology;
|
||||
|
||||
mod acknowledgement_listener;
|
||||
mod input_message_listener;
|
||||
mod retransmission_request_listener;
|
||||
mod sent_notification_listener;
|
||||
|
||||
type RetransmissionRequestSender = mpsc::UnboundedSender<FragmentIdentifier>;
|
||||
type RetransmissionRequestReceiver = mpsc::UnboundedReceiver<FragmentIdentifier>;
|
||||
|
||||
pub(super) type SentPacketNotificationSender = mpsc::UnboundedSender<FragmentIdentifier>;
|
||||
type SentPacketNotificationReceiver = mpsc::UnboundedReceiver<FragmentIdentifier>;
|
||||
|
||||
type PendingAcksMap = Arc<RwLock<HashMap<FragmentIdentifier, PendingAcknowledgement>>>;
|
||||
|
||||
struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: Delay,
|
||||
recipient: Recipient,
|
||||
retransmission_cancel: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl PendingAcknowledgement {
|
||||
fn new(message_chunk: Fragment, delay: Delay, recipient: Recipient) -> Self {
|
||||
PendingAcknowledgement {
|
||||
message_chunk,
|
||||
delay,
|
||||
retransmission_cancel: Arc::new(Notify::new()),
|
||||
recipient,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_delay(&mut self, new_delay: Delay) {
|
||||
self.delay = new_delay;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementControllerConnectors {
|
||||
real_message_sender: RealMessageSender,
|
||||
input_receiver: InputMessageReceiver,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
}
|
||||
|
||||
impl AcknowledgementControllerConnectors {
|
||||
pub(super) fn new(
|
||||
real_message_sender: RealMessageSender,
|
||||
input_receiver: InputMessageReceiver,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
) -> Self {
|
||||
AcknowledgementControllerConnectors {
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier,
|
||||
ack_receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementController<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
acknowledgement_listener: Option<AcknowledgementListener>,
|
||||
input_message_listener: Option<InputMessageListener<R, T>>,
|
||||
retransmission_request_listener: Option<RetransmissionRequestListener<R, T>>,
|
||||
sent_notification_listener: Option<SentNotificationListener>,
|
||||
}
|
||||
|
||||
impl<R, T> AcknowledgementController<R, T>
|
||||
where
|
||||
R: 'static + CryptoRng + Rng + Clone + Send,
|
||||
T: 'static + NymTopology,
|
||||
{
|
||||
pub(super) fn new(
|
||||
mut rng: R,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
ack_recipient: Recipient,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
) -> Self {
|
||||
// note for future-self: perhaps for key rotation we could replace it with Arc<AtomicCell<Key>> ?
|
||||
// actually same could be true for any keys we use
|
||||
let ack_key = Arc::new(acknowledgements::generate_key(&mut rng));
|
||||
let pending_acks = Arc::new(RwLock::new(HashMap::new()));
|
||||
let message_chunker = MessageChunker::new_with_rng(
|
||||
rng,
|
||||
ack_recipient.clone(),
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
);
|
||||
|
||||
let acknowledgement_listener = AcknowledgementListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
connectors.ack_receiver,
|
||||
Arc::clone(&pending_acks),
|
||||
);
|
||||
|
||||
let input_message_listener = InputMessageListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient.clone(),
|
||||
connectors.input_receiver,
|
||||
message_chunker.clone(),
|
||||
Arc::clone(&pending_acks),
|
||||
connectors.real_message_sender.clone(),
|
||||
topology_access.clone(),
|
||||
);
|
||||
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
let retransmission_request_listener = RetransmissionRequestListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient,
|
||||
message_chunker,
|
||||
Arc::clone(&pending_acks),
|
||||
connectors.real_message_sender,
|
||||
retransmission_rx,
|
||||
topology_access,
|
||||
);
|
||||
|
||||
let sent_notification_listener = SentNotificationListener::new(
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
connectors.sent_notifier,
|
||||
pending_acks,
|
||||
retransmission_tx,
|
||||
);
|
||||
|
||||
AcknowledgementController {
|
||||
ack_key,
|
||||
acknowledgement_listener: Some(acknowledgement_listener),
|
||||
input_message_listener: Some(input_message_listener),
|
||||
retransmission_request_listener: Some(retransmission_request_listener),
|
||||
sent_notification_listener: Some(sent_notification_listener),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ack_key(&self) -> Arc<AckAes128Key> {
|
||||
Arc::clone(&self.ack_key)
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener.take().unwrap();
|
||||
let mut input_message_listener = self.input_message_listener.take().unwrap();
|
||||
let mut retransmission_request_listener =
|
||||
self.retransmission_request_listener.take().unwrap();
|
||||
let mut sent_notification_listener = self.sent_notification_listener.take().unwrap();
|
||||
|
||||
// TODO: perhaps an extra 'DEBUG' task that would periodically check for stale entries in
|
||||
// pending acks map?
|
||||
// It would only be 'DEBUG' as I don't expect any stale entries to exist there to begin with,
|
||||
// but when can bugs be expected to begin with?
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let ack_listener_fut = tokio::spawn(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
error!("The acknowledgement listener has finished execution!");
|
||||
acknowledgement_listener
|
||||
});
|
||||
let input_listener_fut = tokio::spawn(async move {
|
||||
input_message_listener.run().await;
|
||||
error!("The input listener has finished execution!");
|
||||
input_message_listener
|
||||
});
|
||||
let retransmission_req_fut = tokio::spawn(async move {
|
||||
retransmission_request_listener.run().await;
|
||||
error!("The retransmission request listener has finished execution!");
|
||||
retransmission_request_listener
|
||||
});
|
||||
let sent_notification_fut = tokio::spawn(async move {
|
||||
sent_notification_listener.run().await;
|
||||
error!("The sent notification listener has finished execution!");
|
||||
sent_notification_listener
|
||||
});
|
||||
|
||||
// technically we don't have to bring `AcknowledgementController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.acknowledgement_listener = Some(ack_listener_fut.await.unwrap());
|
||||
self.input_message_listener = Some(input_listener_fut.await.unwrap());
|
||||
self.retransmission_request_listener = Some(retransmission_req_fut.await.unwrap());
|
||||
self.sent_notification_listener = Some(sent_notification_fut.await.unwrap());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcksMap, RetransmissionRequestReceiver};
|
||||
use crate::client::{
|
||||
real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckAes128Key,
|
||||
addressing::clients::Recipient,
|
||||
chunking::{fragment::FragmentIdentifier, MessageChunker},
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use topology::NymTopology;
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_recipient: Recipient,
|
||||
message_chunker: MessageChunker<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
impl<R, T> RetransmissionRequestListener<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
ack_recipient: Recipient,
|
||||
message_chunker: MessageChunker<R>,
|
||||
pending_acks: PendingAcksMap,
|
||||
real_message_sender: RealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
message_chunker,
|
||||
pending_acks,
|
||||
real_message_sender,
|
||||
request_receiver,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(&mut self, frag_id: FragmentIdentifier) {
|
||||
let pending_acks_map_read_guard = self.pending_acks.read().await;
|
||||
// if the unwrap failed here, we have some weird bug somewhere - honestly, I'm not sure
|
||||
// if it's even possible for it to happen
|
||||
let unreceived_ack_fragment = pending_acks_map_read_guard
|
||||
.get(&frag_id)
|
||||
.expect("wanted to retransmit ack'd fragment");
|
||||
|
||||
let packet_recipient = unreceived_ack_fragment.recipient.clone();
|
||||
let chunk_clone = unreceived_ack_fragment.message_chunk.clone();
|
||||
let frag_id = unreceived_ack_fragment.message_chunk.fragment_identifier();
|
||||
|
||||
// TODO: we need some proper benchmarking here to determine whether it could
|
||||
// be more efficient to just get write lock and keep it while doing sphinx computation,
|
||||
// but my gut feeling tells me we should re-acquire it.
|
||||
drop(pending_acks_map_read_guard);
|
||||
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology_ref_option =
|
||||
topology_permit.try_get_valid_topology_ref(&self.ack_recipient, &packet_recipient);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("Could not retransmit the packet - the network topology is invalid");
|
||||
// TODO: perhaps put back into pending acks and reset the timer?
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
let (total_delay, (first_hop, packet)) = self
|
||||
.message_chunker
|
||||
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, &packet_recipient)
|
||||
.unwrap();
|
||||
|
||||
// minor optimization to not hold the permit while we no longer need it and might have to block
|
||||
// waiting for the write lock on `pending_acks`
|
||||
drop(topology_permit);
|
||||
|
||||
self.pending_acks
|
||||
.write()
|
||||
.await
|
||||
.get_mut(&frag_id)
|
||||
.expect(
|
||||
"on_retransmission_request: somehow we already received an ack for this packet?",
|
||||
)
|
||||
.update_delay(total_delay);
|
||||
|
||||
self.real_message_sender
|
||||
.unbounded_send(RealMessage::new(first_hop, packet, frag_id))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started RetransmissionRequestListener");
|
||||
while let Some(frag_id) = self.request_receiver.next().await {
|
||||
self.on_retransmission_request(frag_id).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{PendingAcksMap, RetransmissionRequestSender, SentPacketNotificationReceiver};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::chunking::fragment::FragmentIdentifier;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// responsible for starting and controlling retransmission timers
|
||||
// it is required because when we send our packet to the `real traffic stream` controlled
|
||||
// with poisson timer, there's no guarantee the message will be sent immediately, so we might
|
||||
// accidentally fire retransmission way quicker than we would have wanted.
|
||||
pub(super) struct SentNotificationListener {
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
pending_acks: PendingAcksMap,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
sent_notifier,
|
||||
pending_acks,
|
||||
retransmission_sender,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_sent_message(&mut self, frag_id: FragmentIdentifier) {
|
||||
let pending_acks_map_read_guard = self.pending_acks.read().await;
|
||||
// if the unwrap failed here, we have some weird bug somewhere
|
||||
// although when I think about it, it *theoretically* could happen under extremely heavy client
|
||||
// load that `on_sent_message()` is not called (and we do not receive the read permit)
|
||||
// until we already received and processed an ack for the packet
|
||||
// but this seems extremely unrealistic, but perhaps we should guard against that?
|
||||
let pending_ack_data = pending_acks_map_read_guard
|
||||
.get(&frag_id)
|
||||
.expect("on_sent_message: somehow we already received an ack for this packet?");
|
||||
|
||||
// if this assertion ever fails, we have some bug due to some unintended leak.
|
||||
// the only reason I see it could happen if the `tokio::select` in the spawned
|
||||
// task below somehow did not drop it
|
||||
debug_assert_eq!(
|
||||
Arc::strong_count(&pending_ack_data.retransmission_cancel),
|
||||
1
|
||||
);
|
||||
|
||||
// TODO: read more about Arc::downgrade. it could be useful here
|
||||
let retransmission_cancel = Arc::clone(&pending_ack_data.retransmission_cancel);
|
||||
|
||||
let retransmission_timeout = tokio::time::delay_for(
|
||||
(pending_ack_data.delay.clone() * self.ack_wait_multiplier).to_duration()
|
||||
+ self.ack_wait_addition,
|
||||
);
|
||||
|
||||
let retransmission_sender = self.retransmission_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = retransmission_cancel.notified() => {
|
||||
trace!("received ack for the fragment. Cancelling retransmission future");
|
||||
}
|
||||
_ = retransmission_timeout => {
|
||||
trace!("did not receive an ack - will retransmit the packet");
|
||||
retransmission_sender.unbounded_send(frag_id).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started SentNotificationListener");
|
||||
while let Some(frag_id) = self.sent_notifier.next().await {
|
||||
self.on_sent_message(frag_id).await;
|
||||
}
|
||||
error!("TODO: error msg. Or maybe panic?")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// INPUT: InputMessage from user
|
||||
// INPUT2: Acks from mix
|
||||
// OUTPUT: MixMessage to mix traffic
|
||||
|
||||
use self::{
|
||||
acknowlegement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||
};
|
||||
use crate::client::real_messages_control::acknowlegement_control::AcknowledgementControllerConnectors;
|
||||
use crate::client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::MixMessageSender,
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::identifier::AckAes128Key;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::NymTopology;
|
||||
|
||||
mod acknowlegement_control;
|
||||
mod real_traffic_stream;
|
||||
|
||||
pub(crate) struct Config {
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
self_recipient: Recipient,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
average_packet_delay_duration: Duration,
|
||||
self_recipient: Recipient,
|
||||
) -> Self {
|
||||
Config {
|
||||
self_recipient,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
average_message_sending_delay,
|
||||
ack_wait_multiplier,
|
||||
ack_wait_addition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessagesController<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
out_queue_control: Option<OutQueueControl<R, T>>,
|
||||
ack_control: Option<AcknowledgementController<R, T>>,
|
||||
}
|
||||
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl<T: 'static + NymTopology> RealMessagesController<OsRng, T> {
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: MixMessageSender,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
let (real_message_sender, real_message_receiver) = mpsc::unbounded();
|
||||
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
|
||||
|
||||
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier_rx,
|
||||
ack_receiver,
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
rng,
|
||||
topology_access.clone(),
|
||||
config.self_recipient.clone(),
|
||||
config.average_packet_delay_duration,
|
||||
config.average_ack_delay_duration,
|
||||
config.ack_wait_multiplier,
|
||||
config.ack_wait_addition,
|
||||
ack_controller_connectors,
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
ack_control.ack_key(),
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
config.average_message_sending_delay,
|
||||
sent_notifier_tx,
|
||||
mix_sender,
|
||||
real_message_receiver,
|
||||
rng,
|
||||
config.self_recipient,
|
||||
topology_access,
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
out_queue_control: Some(out_queue_control),
|
||||
ack_control: Some(ack_control),
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this method can only be called before `run` is called
|
||||
pub(super) fn ack_key(&self) -> Arc<AckAes128Key> {
|
||||
self.ack_control.as_ref().unwrap().ack_key()
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut out_queue_control = self.out_queue_control.take().unwrap();
|
||||
let mut ack_control = self.ack_control.take().unwrap();
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let out_queue_control_fut = tokio::spawn(async move {
|
||||
out_queue_control.run_out_queue_control().await;
|
||||
error!("The out queue controller has finished execution!");
|
||||
out_queue_control
|
||||
});
|
||||
let ack_control_fut = tokio::spawn(async move {
|
||||
ack_control.run().await;
|
||||
error!("The acknowledgement controller has finished execution!");
|
||||
ack_control
|
||||
});
|
||||
|
||||
// technically we don't have to bring `RealMessagesController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.out_queue_control = Some(out_queue_control_fut.await.unwrap());
|
||||
self.ack_control = Some(ack_control_fut.await.unwrap());
|
||||
}
|
||||
|
||||
// &Handle is only passed for consistency sake with other client modules, but I think
|
||||
// when we get to refactoring, we should apply gateway approach and make it implicit
|
||||
pub(super) fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::mix_traffic::{MixMessage, MixMessageSender};
|
||||
use crate::client::real_messages_control::acknowlegement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::identifier::AckAes128Key;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use nymsphinx::SphinxPacket;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) struct OutQueueControl<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
T: NymTopology,
|
||||
{
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Average delay between sending subsequent packets.
|
||||
average_message_sending_delay: Duration,
|
||||
|
||||
/// Channel used for notifying of a real packet being sent out. Used to start up retransmission timer.
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: time::Delay,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: MixMessageSender,
|
||||
|
||||
/// Channel used for receiving real, prepared, messages that must be first sufficiently delayed
|
||||
/// before being sent out into the network.
|
||||
real_receiver: RealMessageReceiver,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessage {
|
||||
first_hop_address: SocketAddr,
|
||||
packet: SphinxPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
}
|
||||
|
||||
impl RealMessage {
|
||||
pub(crate) fn new(
|
||||
first_hop_address: SocketAddr,
|
||||
packet: SphinxPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
) -> Self {
|
||||
RealMessage {
|
||||
first_hop_address,
|
||||
packet,
|
||||
fragment_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
|
||||
// after sufficient delay
|
||||
pub(crate) type RealMessageSender = mpsc::UnboundedSender<RealMessage>;
|
||||
type RealMessageReceiver = mpsc::UnboundedReceiver<RealMessage>;
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
Real(RealMessage),
|
||||
}
|
||||
|
||||
impl<R, T> Stream for OutQueueControl<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
T: NymTopology, // this really confuses me, why T doesn't need to be Unpin?
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.average_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there's an actual message - return it
|
||||
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, T> OutQueueControl<R, T>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
T: NymTopology,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckAes128Key>,
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
mix_tx: MixMessageSender,
|
||||
real_receiver: RealMessageReceiver,
|
||||
rng: R,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
sent_notifier,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
real_receiver,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_message = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
// 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(
|
||||
&self.our_full_destination,
|
||||
&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();
|
||||
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&*self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
MixMessage::new(cover_message.0, cover_message.1)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
// well technically the message was not sent just yet, but now it's up to internal
|
||||
// queues and client load rather than the required delay. So realistically we can treat
|
||||
// whatever is about to happen as negligible additional delay.
|
||||
self.sent_notifier
|
||||
.unbounded_send(real_message.fragment_id)
|
||||
.unwrap();
|
||||
MixMessage::new(real_message.first_hop_address, real_message.packet)
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx.unbounded_send(next_message).unwrap();
|
||||
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
// ready and hence was immediately re-scheduled causing other tasks to be starved;
|
||||
// yield makes it go back the scheduling queue regardless of its value availability
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_out_queue_control(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = time::delay_for(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.average_message_sending_delay,
|
||||
));
|
||||
|
||||
info!("Starting out queue controller...");
|
||||
while let Some(next_message) = self.next().await {
|
||||
self.on_message(next_message).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::client::inbound_messages::InputMessage;
|
||||
use crate::client::mix_traffic::MixMessage;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::{error, info, trace, warn};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::utils::{encapsulation, poisson};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub(crate) struct OutQueueControl<T: NymTopology> {
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
next_delay: time::Delay,
|
||||
mix_tx: mpsc::UnboundedSender<MixMessage>,
|
||||
input_rx: mpsc::UnboundedReceiver<InputMessage>,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
}
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
Real(InputMessage),
|
||||
}
|
||||
|
||||
impl<T: NymTopology> Stream for OutQueueControl<T> {
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if Pin::new(&mut self.next_delay).poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = poisson::sample(self.average_message_sending_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.reset(next);
|
||||
|
||||
// decide what kind of message to send
|
||||
match Pin::new(&mut self.input_rx).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there's an actual message - return it
|
||||
Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))),
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + NymTopology> OutQueueControl<T> {
|
||||
pub(crate) fn new(
|
||||
mix_tx: mpsc::UnboundedSender<MixMessage>,
|
||||
input_rx: mpsc::UnboundedReceiver<InputMessage>,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor<T>,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
next_delay: time::delay_for(Default::default()),
|
||||
mix_tx,
|
||||
input_rx,
|
||||
our_full_destination,
|
||||
topology_access,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_route(&self, other_recipient: Option<&Recipient>) -> Option<Vec<nymsphinx::Node>> {
|
||||
match other_recipient {
|
||||
// we are sending to ourselves
|
||||
None => {
|
||||
self.topology_access
|
||||
.random_route_to_gateway(&self.our_full_destination.gateway())
|
||||
.await
|
||||
}
|
||||
Some(other_recipient) => {
|
||||
self.topology_access
|
||||
.random_route_to_gateway(&other_recipient.gateway())
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_packet = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let route = self.get_route(None).await;
|
||||
if route.is_none() {
|
||||
warn!("No valid topology detected - won't send any real or loop message this time");
|
||||
return;
|
||||
}
|
||||
let route = route.unwrap();
|
||||
encapsulation::loop_cover_message_route(
|
||||
self.our_full_destination.destination().clone(),
|
||||
route,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
let (recipient, data) = real_message.destruct();
|
||||
let route = self.get_route(Some(&recipient)).await;
|
||||
if route.is_none() {
|
||||
warn!("No valid topology detected - won't send any real or loop message this time");
|
||||
return;
|
||||
}
|
||||
let route = route.unwrap();
|
||||
encapsulation::encapsulate_message_route(
|
||||
recipient.destination(),
|
||||
data,
|
||||
route,
|
||||
self.average_packet_delay,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let next_packet = match next_packet {
|
||||
Ok(message) => message,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Somehow we managed to create an invalid traffic message - {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx
|
||||
.unbounded_send(MixMessage::new(next_packet.0, next_packet.1))
|
||||
.unwrap();
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
// ready and hence was immediately re-scheduled causing other tasks to be starved;
|
||||
// yield makes it go back the scheduling queue regardless of its value availability
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_out_queue_control(mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = time::delay_for(poisson::sample(self.average_message_sending_delay));
|
||||
|
||||
info!("Starting out queue controller...");
|
||||
while let Some(next_message) = self.next().await {
|
||||
self.on_message(next_message).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move { self.run_out_queue_control().await })
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::lock::{Mutex, MutexGuard};
|
||||
use futures::StreamExt;
|
||||
use gateway_client::SphinxPacketReceiver;
|
||||
use gateway_client::MixnetMessageReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
chunking::reconstruction::MessageReconstructor,
|
||||
utils::encapsulation::LOOP_COVER_MESSAGE_PAYLOAD,
|
||||
};
|
||||
use nymsphinx::chunking::reconstruction::MessageReconstructor;
|
||||
use nymsphinx::cover::LOOP_COVER_MESSAGE_PAYLOAD;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -38,6 +37,11 @@ struct ReceivedMessagesBufferInner {
|
||||
messages: Vec<Vec<u8>>,
|
||||
message_reconstructor: MessageReconstructor,
|
||||
message_sender: Option<ReconstructedMessagesSender>,
|
||||
|
||||
// TODO: this will get cleared upon re-running the client
|
||||
// but perhaps it should be changed to include timestamps of when the message was reconstructed
|
||||
// and every now and then remove ids older than X
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -54,6 +58,7 @@ impl ReceivedMessagesBuffer {
|
||||
messages: Vec::new(),
|
||||
message_reconstructor: MessageReconstructor::new(),
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -102,6 +107,47 @@ impl ReceivedMessagesBuffer {
|
||||
self.inner.lock().await.messages.extend(msgs)
|
||||
}
|
||||
|
||||
fn process_received_fragment(
|
||||
mutex_guard: &mut MutexGuard<ReceivedMessagesBufferInner>,
|
||||
raw_fragment: Vec<u8>,
|
||||
) -> Option<Vec<u8>> {
|
||||
if raw_fragment == LOOP_COVER_MESSAGE_PAYLOAD {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
return None;
|
||||
}
|
||||
|
||||
let fragment = match mutex_guard
|
||||
.message_reconstructor
|
||||
.recover_fragment(raw_fragment)
|
||||
{
|
||||
Err(e) => {
|
||||
warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
if mutex_guard.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(reconstructed_message) = mutex_guard
|
||||
.message_reconstructor
|
||||
.insert_new_fragment(fragment)
|
||||
{
|
||||
let (completed_message, message_sets) = reconstructed_message;
|
||||
for set_id in message_sets {
|
||||
if !mutex_guard.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
error!("Reconstructed another message containing already used set id!")
|
||||
}
|
||||
}
|
||||
return Some(completed_message);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn add_new_message_fragments(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!(
|
||||
"Adding {:?} new message fragments to the buffer!",
|
||||
@@ -112,15 +158,10 @@ impl ReceivedMessagesBuffer {
|
||||
let mut completed_messages = Vec::new();
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
for msg_fragment in msgs {
|
||||
if msg_fragment == LOOP_COVER_MESSAGE_PAYLOAD {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(reconstructed_message) =
|
||||
inner_guard.message_reconstructor.new_fragment(msg_fragment)
|
||||
if let Some(completed_message) =
|
||||
Self::process_received_fragment(&mut inner_guard, msg_fragment)
|
||||
{
|
||||
completed_messages.push(reconstructed_message);
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,22 +230,22 @@ impl RequestReceiver {
|
||||
|
||||
struct FragmentedMessageReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
}
|
||||
|
||||
impl FragmentedMessageReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
sphinx_packet_receiver,
|
||||
mixnet_packet_receiver,
|
||||
}
|
||||
}
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(new_messages) = self.sphinx_packet_receiver.next().await {
|
||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
|
||||
self.received_buffer
|
||||
.add_new_message_fragments(new_messages)
|
||||
.await;
|
||||
@@ -214,21 +255,21 @@ impl FragmentedMessageReceiver {
|
||||
}
|
||||
|
||||
pub(crate) struct ReceivedMessagesBufferController {
|
||||
fragmented_messsage_receiver: FragmentedMessageReceiver,
|
||||
fragmented_message_receiver: FragmentedMessageReceiver,
|
||||
request_receiver: RequestReceiver,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferController {
|
||||
pub(crate) fn new(
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
sphinx_packet_receiver: SphinxPacketReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new();
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_messsage_receiver: FragmentedMessageReceiver::new(
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
sphinx_packet_receiver,
|
||||
mixnet_packet_receiver,
|
||||
),
|
||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||
}
|
||||
@@ -236,7 +277,7 @@ impl ReceivedMessagesBufferController {
|
||||
|
||||
pub(crate) fn start(self, handle: &Handle) {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.fragmented_messsage_receiver.start(handle);
|
||||
self.fragmented_message_receiver.start(handle);
|
||||
self.request_receiver.start(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,29 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::built_info;
|
||||
use futures::lock::Mutex;
|
||||
use log::*;
|
||||
use nymsphinx::NodeAddressBytes;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::{provider, NymTopology};
|
||||
|
||||
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
|
||||
#[derive(Debug)]
|
||||
struct TopologyAccessorInner<T: NymTopology>(Option<T>);
|
||||
|
||||
impl<T: NymTopology> Deref for TopologyAccessorInner<T> {
|
||||
type Target = Option<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NymTopology> TopologyAccessorInner<T> {
|
||||
fn new() -> Self {
|
||||
TopologyAccessorInner(None)
|
||||
@@ -35,26 +46,78 @@ impl<T: NymTopology> TopologyAccessorInner<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TopologyReadPermit<'a, T: NymTopology> {
|
||||
permit: RwLockReadGuard<'a, TopologyAccessorInner<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T: NymTopology> TopologyReadPermit<'a, T> {
|
||||
/// Using provided topology read permit, tries to get an immutable reference to the underlying
|
||||
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
|
||||
pub(super) fn try_get_valid_topology_ref(
|
||||
&'a self,
|
||||
ack_recipient: &Recipient,
|
||||
packet_recipient: &Recipient,
|
||||
) -> Option<&'a T> {
|
||||
// first we need to deref out of RwLockReadGuard
|
||||
// then we need to deref out of TopologyAccessorInner
|
||||
// then we must take ref of option, i.e. Option<&T>
|
||||
// and finally try to unwrap it to obtain &T
|
||||
let topology_ref_option = (*self.permit.deref()).as_ref();
|
||||
|
||||
if topology_ref_option.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
// see if it's possible to route the packet to both gateways
|
||||
if !topology_ref.can_construct_path_through()
|
||||
|| !topology_ref.gateway_exists(&packet_recipient.gateway())
|
||||
|| !topology_ref.gateway_exists(&ack_recipient.gateway())
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(topology_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: NymTopology> From<RwLockReadGuard<'a, TopologyAccessorInner<T>>>
|
||||
for TopologyReadPermit<'a, T>
|
||||
{
|
||||
fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner<T>>) -> Self {
|
||||
TopologyReadPermit {
|
||||
permit: read_permit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TopologyAccessor<T: NymTopology> {
|
||||
// TODO: this requires some actual benchmarking to determine if obtaining mutex is not going
|
||||
// to cause some bottlenecking and whether perhaps RwLock would be better
|
||||
inner: Arc<Mutex<TopologyAccessorInner<T>>>,
|
||||
// `RwLock` *seems to* be the better approach for this as write access is only requested every
|
||||
// few seconds, while reads are needed every single packet generated.
|
||||
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
|
||||
// approach than a `Mutex`
|
||||
inner: Arc<RwLock<TopologyAccessorInner<T>>>,
|
||||
}
|
||||
|
||||
impl<T: NymTopology> TopologyAccessor<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
TopologyAccessor {
|
||||
inner: Arc::new(Mutex::new(TopologyAccessorInner::new())),
|
||||
inner: Arc::new(RwLock::new(TopologyAccessorInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn get_read_permit(&self) -> TopologyReadPermit<'_, T> {
|
||||
self.inner.read().await.into()
|
||||
}
|
||||
|
||||
async fn update_global_topology(&mut self, new_topology: Option<T>) {
|
||||
self.inner.lock().await.update(new_topology);
|
||||
self.inner.write().await.update(new_topology);
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateway_socket_url(&self, id: &str) -> Option<String> {
|
||||
match &self.inner.lock().await.0 {
|
||||
match &self.inner.read().await.0 {
|
||||
None => None,
|
||||
Some(ref topology) => topology
|
||||
.gateways()
|
||||
@@ -67,7 +130,7 @@ impl<T: NymTopology> TopologyAccessor<T> {
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because healthchecker is disabled due to required changes)
|
||||
pub(crate) async fn is_routable(&self) -> bool {
|
||||
match &self.inner.lock().await.0 {
|
||||
match &self.inner.read().await.0 {
|
||||
None => false,
|
||||
Some(ref topology) => topology.can_construct_path_through(),
|
||||
}
|
||||
@@ -75,7 +138,7 @@ impl<T: NymTopology> TopologyAccessor<T> {
|
||||
|
||||
pub(crate) async fn get_all_clients(&self) -> Option<Vec<provider::Client>> {
|
||||
// TODO: this will need to be modified to instead return pairs (provider, client)
|
||||
match &self.inner.lock().await.0 {
|
||||
match &self.inner.read().await.0 {
|
||||
None => None,
|
||||
Some(ref topology) => Some(
|
||||
topology
|
||||
@@ -87,40 +150,6 @@ impl<T: NymTopology> TopologyAccessor<T> {
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn random_route_to_gateway(
|
||||
&self,
|
||||
gateway: &NodeAddressBytes,
|
||||
) -> Option<Vec<nymsphinx::Node>> {
|
||||
let b58_address = gateway.to_base58_string();
|
||||
let guard = self.inner.lock().await;
|
||||
let topology = guard.0.as_ref()?;
|
||||
|
||||
let gateway = topology
|
||||
.gateways()
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|gateway| gateway.pub_key == b58_address.clone())?;
|
||||
|
||||
topology.random_route_to_gateway(gateway.into()).ok()
|
||||
}
|
||||
|
||||
// // this is a rather temporary solution as each client will have an associated provider
|
||||
// // currently that is not implemented yet and there only exists one provider in the network
|
||||
// pub(crate) async fn random_route(&self) -> Option<Vec<nymsphinx::Node>> {
|
||||
// match &self.inner.lock().await.0 {
|
||||
// None => None,
|
||||
// Some(ref topology) => {
|
||||
// let mut gateways = topology.gateways();
|
||||
// if gateways.is_empty() {
|
||||
// return None;
|
||||
// }
|
||||
// // unwrap is fine here as we asserted there is at least single provider
|
||||
// let provider = gateways.pop().unwrap().into();
|
||||
// topology.random_route_to_gateway(provider).ok()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) struct TopologyRefresherConfig {
|
||||
|
||||
@@ -19,7 +19,6 @@ use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::identity::MixIdentityKeyPair;
|
||||
use directory_client::presence::Topology;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::AuthToken;
|
||||
use nymsphinx::DestinationAddressBytes;
|
||||
@@ -68,20 +67,18 @@ async fn try_gateway_registration(
|
||||
gateways: Vec<Node>,
|
||||
our_address: DestinationAddressBytes,
|
||||
) -> Option<(String, AuthToken)> {
|
||||
// TODO: having to do something like this suggests that perhaps GatewayClient's constructor
|
||||
// could be improved
|
||||
let (sphinx_tx, _) = mpsc::unbounded();
|
||||
let timeout = Duration::from_millis(1500);
|
||||
for gateway in gateways {
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
url::Url::parse(&gateway.client_listener).unwrap(),
|
||||
our_address.clone(),
|
||||
None,
|
||||
sphinx_tx.clone(),
|
||||
timeout,
|
||||
);
|
||||
if gateway_client.establish_connection().await.is_ok() {
|
||||
if let Ok(token) = gateway_client.register().await {
|
||||
if let Ok(token) = gateway_client.register_without_listening().await {
|
||||
if let Err(err) = gateway_client.close_connection().await {
|
||||
eprintln!("Error while closing connection to the gateway! - {:?}", err);
|
||||
continue;
|
||||
} else {
|
||||
return Some((gateway.pub_key, token));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ const DEFAULT_LISTENING_PORT: u16 = 1977;
|
||||
const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net";
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
const DEFAULT_ACK_WAIT_ADDITION: u64 = 800;
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; // 1s
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 500; // 0.5s
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 200; // 0.2s
|
||||
@@ -189,6 +191,18 @@ impl Config {
|
||||
time::Duration::from_millis(self.debug.average_packet_delay)
|
||||
}
|
||||
|
||||
pub fn get_average_ack_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.average_ack_delay)
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_multiplier(&self) -> f64 {
|
||||
self.debug.ack_wait_multiplier
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_addition(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.ack_wait_addition)
|
||||
}
|
||||
|
||||
pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay)
|
||||
}
|
||||
@@ -312,6 +326,24 @@ pub struct Debug {
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
average_packet_delay: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
average_ack_delay: u64,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
ack_wait_addition: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
/// The provided value is interpreted as milliseconds.
|
||||
@@ -345,6 +377,9 @@ impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
|
||||
@@ -77,6 +77,7 @@ listening_port = {{ socket.listening_port }}
|
||||
[debug]
|
||||
|
||||
average_packet_delay = {{ debug.average_packet_delay }}
|
||||
average_ack_delay = {{ debug.average_ack_delay }}
|
||||
loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }}
|
||||
message_sending_average_delay = {{ debug.message_sending_average_delay }}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ use futures::channel::mpsc;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::split_and_prepare_payloads;
|
||||
use std::convert::TryFrom;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{
|
||||
@@ -104,12 +103,9 @@ impl<T: NymTopology> Handler<T> {
|
||||
}
|
||||
};
|
||||
|
||||
// in case the message is too long and needs to be split into multiple packets:
|
||||
let split_message = split_and_prepare_payloads(&message_bytes);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
}
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new(recipient, message_bytes);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Text;
|
||||
|
||||
@@ -155,12 +151,9 @@ impl<T: NymTopology> Handler<T> {
|
||||
}
|
||||
|
||||
async fn handle_binary_send(&mut self, recipient: Recipient, data: Vec<u8>) -> ServerResponse {
|
||||
// in case the message is too long and needs to be split into multiple packets:
|
||||
let split_message = split_and_prepare_payloads(&data);
|
||||
for message_fragment in split_message {
|
||||
let input_msg = InputMessage::new(recipient.clone(), message_fragment);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
}
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new(recipient, data);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Binary;
|
||||
ServerResponse::Send
|
||||
|
||||
@@ -55,6 +55,7 @@ pub struct NodeData {
|
||||
///
|
||||
/// Message chunking is currently not implemented. If the message exceeds the
|
||||
/// capacity of a single Sphinx packet, the extra information will be discarded.
|
||||
///
|
||||
#[wasm_bindgen]
|
||||
pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) -> Vec<u8> {
|
||||
utils::set_panic_hook(); // nicer js errors.
|
||||
@@ -73,7 +74,7 @@ pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) ->
|
||||
let message = msg.as_bytes().to_vec();
|
||||
|
||||
let destination = Destination::new(recipient.destination(), Default::default());
|
||||
let sphinx_packet = SphinxPacket::new(message, &route, &destination, &delays, None).unwrap();
|
||||
let sphinx_packet = SphinxPacket::new(message, &route, &destination, &delays).unwrap();
|
||||
payload(sphinx_packet, route)
|
||||
}
|
||||
|
||||
@@ -123,7 +124,7 @@ impl TryFrom<NodeData> for SphinxNode {
|
||||
let src = MixIdentityPublicKey::from_base58_string(node_data.public_key).to_bytes();
|
||||
let mut dest: [u8; 32] = [0; 32];
|
||||
dest.copy_from_slice(&src);
|
||||
nymsphinx::key::new(dest)
|
||||
nymsphinx::public_key_from_bytes(dest)
|
||||
};
|
||||
|
||||
Ok(SphinxNode { address, pub_key })
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use gateway_requests::auth_token::AuthTokenConversionError;
|
||||
use std::fmt::{self, Error, Formatter};
|
||||
use tokio_tungstenite::tungstenite::Error as WsError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayClientError {
|
||||
ConnectionNotEstablished,
|
||||
GatewayError(String),
|
||||
NetworkError(WsError),
|
||||
NoAuthTokenAvailable,
|
||||
ConnectionAbruptlyClosed,
|
||||
MalformedResponse,
|
||||
NotAuthenticated,
|
||||
ConnectionInInvalidState,
|
||||
AuthenticationFailure,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
fn from(err: WsError) -> Self {
|
||||
GatewayClientError::NetworkError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthTokenConversionError> for GatewayClientError {
|
||||
fn from(_err: AuthTokenConversionError) -> Self {
|
||||
GatewayClientError::MalformedResponse
|
||||
}
|
||||
}
|
||||
|
||||
// better human readable representation of the error, mostly so that GatewayClientError
|
||||
// would implement std::error::Error
|
||||
impl fmt::Display for GatewayClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
match self {
|
||||
GatewayClientError::ConnectionNotEstablished => {
|
||||
write!(f, "connection to the gateway is not established")
|
||||
}
|
||||
GatewayClientError::NoAuthTokenAvailable => {
|
||||
write!(f, "no AuthToken was provided or obtained")
|
||||
}
|
||||
GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"),
|
||||
GatewayClientError::NetworkError(err) => {
|
||||
write!(f, "there was a network error - {}", err)
|
||||
}
|
||||
GatewayClientError::ConnectionAbruptlyClosed => {
|
||||
write!(f, "connection was abruptly closed")
|
||||
}
|
||||
GatewayClientError::Timeout => write!(f, "timed out"),
|
||||
GatewayClientError::MalformedResponse => write!(f, "received response was malformed"),
|
||||
GatewayClientError::ConnectionInInvalidState => write!(
|
||||
f,
|
||||
"connection is in an invalid state - please send a bug report"
|
||||
),
|
||||
GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"),
|
||||
GatewayClientError::GatewayError(err) => {
|
||||
write!(f, "gateway returned an error response - {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt};
|
||||
use gateway_requests::auth_token::{AuthToken, AuthTokenConversionError};
|
||||
use futures::{future::BoxFuture, FutureExt, SinkExt, StreamExt};
|
||||
use gateway_requests::auth_token::AuthToken;
|
||||
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
|
||||
use log::*;
|
||||
use nymsphinx::{DestinationAddressBytes, SphinxPacket};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Error, Formatter};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -27,10 +31,13 @@ use tokio::net::TcpStream;
|
||||
use tokio::sync::Notify;
|
||||
use tokio_tungstenite::{
|
||||
connect_async,
|
||||
tungstenite::{client::IntoClientRequest, protocol::Message, Error as WsError},
|
||||
tungstenite::{client::IntoClientRequest, protocol::Message},
|
||||
WebSocketStream,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
pub mod packet_router;
|
||||
|
||||
// TODO: combine the duplicate reading procedure, i.e.
|
||||
/*
|
||||
msg read from conn.next().await:
|
||||
@@ -51,70 +58,10 @@ msg read from conn.next().await:
|
||||
*/
|
||||
|
||||
// TODO: some batching mechanism to allow reading and sending more than a single packet through
|
||||
pub type SphinxPacketSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
pub type SphinxPacketReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
|
||||
// type alias for not having to type the whole thing every single time
|
||||
type WsConn = WebSocketStream<TcpStream>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayClientError {
|
||||
ConnectionNotEstablished,
|
||||
GatewayError(String),
|
||||
NetworkError(WsError),
|
||||
NoAuthTokenAvailable,
|
||||
ConnectionAbruptlyClosed,
|
||||
MalformedResponse,
|
||||
NotAuthenticated,
|
||||
ConnectionInInvalidState,
|
||||
AuthenticationFailure,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
fn from(err: WsError) -> Self {
|
||||
GatewayClientError::NetworkError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthTokenConversionError> for GatewayClientError {
|
||||
fn from(_err: AuthTokenConversionError) -> Self {
|
||||
GatewayClientError::MalformedResponse
|
||||
}
|
||||
}
|
||||
|
||||
// better human readable representation of the error, mostly so that GatewayClientError
|
||||
// would implement std::error::Error
|
||||
impl fmt::Display for GatewayClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
match self {
|
||||
GatewayClientError::ConnectionNotEstablished => {
|
||||
write!(f, "connection to the gateway is not established")
|
||||
}
|
||||
GatewayClientError::NoAuthTokenAvailable => {
|
||||
write!(f, "no AuthToken was provided or obtained")
|
||||
}
|
||||
GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"),
|
||||
GatewayClientError::NetworkError(err) => {
|
||||
write!(f, "there was a network error - {}", err)
|
||||
}
|
||||
GatewayClientError::ConnectionAbruptlyClosed => {
|
||||
write!(f, "connection was abruptly closed")
|
||||
}
|
||||
GatewayClientError::Timeout => write!(f, "timed out"),
|
||||
GatewayClientError::MalformedResponse => write!(f, "received response was malformed"),
|
||||
GatewayClientError::ConnectionInInvalidState => write!(
|
||||
f,
|
||||
"connection is in an invalid state - please send a bug report"
|
||||
),
|
||||
GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"),
|
||||
GatewayClientError::GatewayError(err) => {
|
||||
write!(f, "gateway returned an error response - {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have ownership over sink half of the connection, but the stream is owned
|
||||
// by some other task, however, we can notify it to get the stream back.
|
||||
struct PartiallyDelegated<'a> {
|
||||
@@ -129,9 +76,9 @@ impl<'a> PartiallyDelegated<'a> {
|
||||
// TODO: this can be potentially bad as we have no direct restrictions of ensuring it's called
|
||||
// within tokio runtime. Perhaps we should use the "old" way of passing explicit
|
||||
// runtime handle to the constructor and using that instead?
|
||||
fn split_and_listen_for_sphinx_packets(
|
||||
fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
sphinx_packet_sender: SphinxPacketSender,
|
||||
packet_router: PacketRouter,
|
||||
) -> Result<Self, GatewayClientError> {
|
||||
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
||||
// read control request responses.
|
||||
@@ -140,7 +87,7 @@ impl<'a> PartiallyDelegated<'a> {
|
||||
|
||||
let (sink, mut stream) = conn.split();
|
||||
|
||||
let sphinx_receiver_future = async move {
|
||||
let mixnet_receiver_future = async move {
|
||||
let mut should_return = false;
|
||||
while !should_return {
|
||||
tokio::select! {
|
||||
@@ -161,7 +108,7 @@ impl<'a> PartiallyDelegated<'a> {
|
||||
Message::Binary(bin_msg) => {
|
||||
// TODO: some batching mechanism to allow reading and sending more than
|
||||
// one packet at the time, because the receiver can easily handle it
|
||||
sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap()
|
||||
packet_router.route_received(vec![bin_msg])
|
||||
},
|
||||
// I think that in the future we should perhaps have some sequence number system, i.e.
|
||||
// so each request/reponse pair can be easily identified, so that if messages are
|
||||
@@ -176,7 +123,7 @@ impl<'a> PartiallyDelegated<'a> {
|
||||
Ok(stream)
|
||||
};
|
||||
|
||||
let spawned_boxed_task = tokio::spawn(sphinx_receiver_future)
|
||||
let spawned_boxed_task = tokio::spawn(mixnet_receiver_future)
|
||||
.map(|join_handle| {
|
||||
join_handle.expect("task must have not failed to finish its execution!")
|
||||
})
|
||||
@@ -244,7 +191,7 @@ pub struct GatewayClient<'a, R: IntoClientRequest + Unpin + Clone> {
|
||||
our_address: DestinationAddressBytes,
|
||||
auth_token: Option<AuthToken>,
|
||||
connection: SocketState<'a>,
|
||||
sphinx_packet_sender: SphinxPacketSender,
|
||||
packet_router: PacketRouter,
|
||||
response_timeout_duration: Duration,
|
||||
}
|
||||
|
||||
@@ -263,7 +210,8 @@ where
|
||||
gateway_address: R,
|
||||
our_address: DestinationAddressBytes,
|
||||
auth_token: Option<AuthToken>,
|
||||
sphinx_packet_sender: SphinxPacketSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
@@ -272,11 +220,65 @@ where
|
||||
our_address,
|
||||
auth_token,
|
||||
connection: SocketState::NotConnected,
|
||||
sphinx_packet_sender,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
|
||||
response_timeout_duration,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_init(
|
||||
gateway_address: R,
|
||||
our_address: DestinationAddressBytes,
|
||||
response_timeout_duration: Duration,
|
||||
) -> Self {
|
||||
use futures::channel::mpsc;
|
||||
|
||||
// note: this packet_router is completely invalid in normal circumstances, but "works"
|
||||
// perfectly fine here, because it's not meant to be used
|
||||
let (ack_tx, _) = mpsc::unbounded();
|
||||
let (mix_tx, _) = mpsc::unbounded();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx);
|
||||
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
gateway_address,
|
||||
our_address,
|
||||
auth_token: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_without_listening(&mut self) -> Result<AuthToken, GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
self.establish_connection().await?;
|
||||
}
|
||||
let msg = ClientControlRequest::new_register(self.our_address.clone()).into();
|
||||
let token = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Register { token } => {
|
||||
self.authenticated = true;
|
||||
Ok(AuthToken::try_from_base58_string(token)?)
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub async fn close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
if self.connection.is_partially_delegated() {
|
||||
self.recover_socket_connection().await?;
|
||||
}
|
||||
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => Ok(socket.close(None).await?),
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match connect_async(self.gateway_address.clone()).await {
|
||||
Ok((ws_stream, _)) => ws_stream,
|
||||
@@ -320,7 +322,7 @@ where
|
||||
};
|
||||
match msg {
|
||||
Message::Binary(bin_msg) => {
|
||||
self.sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap()
|
||||
self.packet_router.route_received(vec![bin_msg]);
|
||||
}
|
||||
Message::Text(txt_msg) => {
|
||||
res = Some(ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse));
|
||||
@@ -340,10 +342,10 @@ where
|
||||
&mut self,
|
||||
msg: Message,
|
||||
) -> Result<ServerResponse, GatewayClientError> {
|
||||
let mut should_restart_sphinx_listener = false;
|
||||
let mut should_restart_mixnet_listener = false;
|
||||
if self.connection.is_partially_delegated() {
|
||||
self.recover_socket_connection().await?;
|
||||
should_restart_sphinx_listener = true;
|
||||
should_restart_mixnet_listener = true;
|
||||
}
|
||||
|
||||
let conn = match self.connection {
|
||||
@@ -354,8 +356,8 @@ where
|
||||
conn.send(msg).await?;
|
||||
let response = self.read_control_response().await;
|
||||
|
||||
if should_restart_sphinx_listener {
|
||||
self.start_listening_for_sphinx_packets()?;
|
||||
if should_restart_mixnet_listener {
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
}
|
||||
response
|
||||
}
|
||||
@@ -387,7 +389,7 @@ where
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
self.start_listening_for_sphinx_packets()?;
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
@@ -414,7 +416,7 @@ where
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
self.start_listening_for_sphinx_packets()?;
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
Ok(authenticated)
|
||||
}
|
||||
|
||||
@@ -468,7 +470,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_listening_for_sphinx_packets(&mut self) -> Result<(), GatewayClientError> {
|
||||
fn start_listening_for_mixnet_messages(&mut self) -> Result<(), GatewayClientError> {
|
||||
if self.connection.is_partially_delegated() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -479,9 +481,9 @@ where
|
||||
let partially_delegated =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
PartiallyDelegated::split_and_listen_for_sphinx_packets(
|
||||
PartiallyDelegated::split_and_listen_for_mixnet_messages(
|
||||
conn,
|
||||
self.sphinx_packet_sender.clone(),
|
||||
self.packet_router.clone(),
|
||||
)?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// JS: I personally don't like this name very much, but could not think of anything better.
|
||||
// I will gladly take any suggestions on how to rename this.
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nymsphinx::params::packet_sizes::PacketSize;
|
||||
|
||||
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
|
||||
pub type AcknowledgementSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
pub type AcknowledgementReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct PacketRouter {
|
||||
ack_sender: AcknowledgementSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
}
|
||||
|
||||
impl PacketRouter {
|
||||
pub(super) fn new(
|
||||
ack_sender: AcknowledgementSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
) -> Self {
|
||||
PacketRouter {
|
||||
ack_sender,
|
||||
mixnet_message_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn route_received(&self, unwrapped_packets: Vec<Vec<u8>>) {
|
||||
let mut received_messages = Vec::new();
|
||||
let mut received_acks = Vec::new();
|
||||
|
||||
for received_packet in unwrapped_packets {
|
||||
// TODO: currently this is not true because gateways are removing padding from the packets
|
||||
// but will be fixed soon enough by all other changes in the pipeline
|
||||
// the question is, however, what exactly will gateways be returning instead. payloads?
|
||||
// 'plaintext'?. To be determined later on.
|
||||
// if received_packet.len() == PacketSize::ACKPacket.payload_size() {
|
||||
|
||||
// this is an extremely ugly if statement, but will be improved once things are actually
|
||||
// constant length everywhere
|
||||
if received_packet.len() == 21 {
|
||||
received_acks.push(received_packet);
|
||||
} else {
|
||||
// well, technically all 21 bytes packets will be considered acks which is not
|
||||
// entirely true, but for time being let's stick with it until other changes are
|
||||
// introduced
|
||||
received_messages.push(received_packet);
|
||||
}
|
||||
}
|
||||
|
||||
// due to how we are currently using it, those unwraps can't fail, but if we ever
|
||||
// wanted to make `gateway-client` into some more generic library, we would probably need
|
||||
// to catch that error or something.
|
||||
if !received_messages.is_empty() {
|
||||
trace!("routing 'real'");
|
||||
self.mixnet_message_sender
|
||||
.unbounded_send(received_messages)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if !received_acks.is_empty() {
|
||||
trace!("routing acks");
|
||||
self.ack_sender.unbounded_send(received_acks).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,6 @@ impl PathChecker {
|
||||
&path[..],
|
||||
&self.our_destination,
|
||||
&delays,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.5"
|
||||
log = "0.4.8"
|
||||
rand = {version = "0.7.3", features = ["wasm-bindgen"]}
|
||||
rand_distr = "0.2.2"
|
||||
tokio-util = { version = "0.3.1", features = ["codec"] }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="fcd17932acbd21a76c42a4bf2a42b9f8668f7e1f" }
|
||||
nymsphinx-acknowledgements = { path = "acknowledgements" }
|
||||
nymsphinx-addressing = { path = "addressing" }
|
||||
nymsphinx-chunking = { path = "chunking" }
|
||||
nymsphinx-cover = { path = "cover" }
|
||||
nymsphinx-framing = { path = "framing" }
|
||||
nymsphinx-params = { path = "params" }
|
||||
nymsphinx-types = { path = "types" }
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nymsphinx-acknowledgements"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes-ctr = "0.4.0"
|
||||
rand = {version = "0.7.3", features = ["wasm-bindgen"]}
|
||||
|
||||
|
||||
|
||||
nymsphinx-addressing = { path = "../addressing" }
|
||||
nymsphinx-params = { path = "../params" }
|
||||
nymsphinx-types = { path = "../types" }
|
||||
|
||||
topology = { path = "../../topology" }
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use aes_ctr::{
|
||||
stream_cipher::{
|
||||
generic_array::{
|
||||
typenum::{marker_traits::Unsigned, U16},
|
||||
GenericArray,
|
||||
},
|
||||
NewStreamCipher, SyncStreamCipher,
|
||||
},
|
||||
Aes128Ctr,
|
||||
};
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
// the 'U16' type is taken directly from the `Ctr128` for consistency sake
|
||||
pub type Aes128KeySize = U16;
|
||||
pub type Aes128NonceSize = U16;
|
||||
|
||||
pub type AckAes128Key = GenericArray<u8, Aes128KeySize>;
|
||||
type AckAes128IV = GenericArray<u8, Aes128NonceSize>;
|
||||
|
||||
pub fn generate_key<R: RngCore + CryptoRng>(rng: &mut R) -> AckAes128Key {
|
||||
let mut ack_key = GenericArray::default();
|
||||
rng.fill_bytes(&mut ack_key);
|
||||
ack_key
|
||||
}
|
||||
|
||||
fn random_iv<R: RngCore + CryptoRng>(rng: &mut R) -> AckAes128IV {
|
||||
let mut iv = GenericArray::default();
|
||||
rng.fill_bytes(&mut iv);
|
||||
iv
|
||||
}
|
||||
|
||||
pub fn prepare_identifier<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
key: &AckAes128Key,
|
||||
marshaled_id: &[u8],
|
||||
) -> Vec<u8> {
|
||||
// TODO: should we have some length checks on the id?
|
||||
|
||||
let iv = random_iv(rng);
|
||||
let mut cipher = Aes128Ctr::new(key, &iv);
|
||||
let mut output = marshaled_id.to_vec();
|
||||
|
||||
cipher.apply_keystream(&mut output);
|
||||
|
||||
iv.into_iter().chain(output.into_iter()).collect()
|
||||
}
|
||||
|
||||
pub fn recover_identifier(key: &AckAes128Key, iv_ciphertext: &[u8]) -> Option<Vec<u8>> {
|
||||
// first few bytes are expected to be the concatenated IV. It must be followed by at least 1 more
|
||||
// byte that we wish to recover, but it can be no longer from what we can physically store inside
|
||||
// an ack
|
||||
if iv_ciphertext.len() <= Aes128NonceSize::to_usize()
|
||||
|| iv_ciphertext.len() > PacketSize::ACKPacket.plaintext_size()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let iv = GenericArray::from_slice(&iv_ciphertext[..Aes128NonceSize::to_usize()]);
|
||||
let mut cipher = Aes128Ctr::new(key, &iv);
|
||||
let mut output = iv_ciphertext[Aes128NonceSize::to_usize()..].to_vec();
|
||||
cipher.apply_keystream(&mut output);
|
||||
|
||||
Some(output)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[test]
|
||||
fn id_is_recoverable() {
|
||||
let mut rng = OsRng;
|
||||
let key = generate_key(&mut rng);
|
||||
|
||||
let id1 = vec![42]; // single byte case
|
||||
let id2 = vec![1, 2, 3, 4, 5]; // 5byte we expect to use
|
||||
let id3 = vec![42; 8]; // some reasonable upper bound id size we could use later on
|
||||
|
||||
let iv_ciphertext1 = prepare_identifier(&mut rng, &key, &id1);
|
||||
let iv_ciphertext2 = prepare_identifier(&mut rng, &key, &id2);
|
||||
let iv_ciphertext3 = prepare_identifier(&mut rng, &key, &id3);
|
||||
|
||||
assert_eq!(id1, recover_identifier(&key, &iv_ciphertext1).unwrap());
|
||||
assert_eq!(id2, recover_identifier(&key, &iv_ciphertext2).unwrap());
|
||||
assert_eq!(id3, recover_identifier(&key, &iv_ciphertext3).unwrap());
|
||||
}
|
||||
}
|
||||
+3
-9
@@ -12,13 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use rand_distr::{Distribution, Exp};
|
||||
use std::time;
|
||||
pub mod identifier;
|
||||
pub mod surb_ack;
|
||||
|
||||
pub fn sample(average_duration: time::Duration) -> time::Duration {
|
||||
// this is our internal code used by our traffic streams
|
||||
// the error is only thrown if average delay is less than 0, which will never happen
|
||||
// so call to unwrap is perfectly safe here
|
||||
let exp = Exp::new(1.0 / average_duration.as_nanos() as f64).unwrap();
|
||||
time::Duration::from_nanos(exp.sample(&mut rand::thread_rng()).round() as u64)
|
||||
}
|
||||
pub use identifier::{generate_key, AckAes128Key};
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::identifier::{prepare_identifier, AckAes128Key};
|
||||
use nymsphinx_addressing::clients::Recipient;
|
||||
use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use nymsphinx_types::builder::SphinxPacketBuilder;
|
||||
use nymsphinx_types::{
|
||||
delays::{self, Delay},
|
||||
Destination, SphinxPacket,
|
||||
};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
use std::time;
|
||||
use topology::{NymTopology, NymTopologyError};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub struct SURBAck {
|
||||
surb_ack_packet: SphinxPacket,
|
||||
first_hop_address: NymNodeRoutingAddress,
|
||||
expected_total_delay: Delay,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SURBAckRecoveryError {
|
||||
InvalidPacketSize,
|
||||
InvalidAddress,
|
||||
InvalidSphinxPacket,
|
||||
}
|
||||
|
||||
impl SURBAck {
|
||||
pub fn construct<R, T>(
|
||||
rng: &mut R,
|
||||
recipient: &Recipient,
|
||||
ack_key: &AckAes128Key,
|
||||
marshaled_fragment_id: &[u8],
|
||||
average_delay: time::Duration,
|
||||
topology: &T,
|
||||
) -> Result<Self, NymTopologyError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
T: NymTopology,
|
||||
{
|
||||
let route = topology.random_route_to_gateway(&recipient.gateway())?;
|
||||
let delays = delays::generate_from_average_duration(route.len(), average_delay);
|
||||
let destination = Destination::new(recipient.destination(), Default::default());
|
||||
|
||||
let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id);
|
||||
|
||||
// once merged, that's an easy rng injection point for sphinx packets : )
|
||||
let surb_ack_packet = SphinxPacketBuilder::new()
|
||||
.with_payload_size(PacketSize::ACKPacket.payload_size())
|
||||
.build_packet(surb_ack_payload, &route, &destination, &delays)
|
||||
.unwrap();
|
||||
|
||||
let expected_total_delay = delays.iter().sum();
|
||||
let first_hop_address =
|
||||
NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap();
|
||||
|
||||
Ok(SURBAck {
|
||||
surb_ack_packet,
|
||||
first_hop_address,
|
||||
expected_total_delay,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn len() -> usize {
|
||||
// TODO: this will be variable once/if we decide to introduce optimization described
|
||||
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
|
||||
PacketSize::ACKPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN
|
||||
}
|
||||
|
||||
pub fn prepare_for_sending(self) -> (Delay, Vec<u8>) {
|
||||
// SURB_FIRST_HOP || SURB_ACK
|
||||
let surb_bytes: Vec<_> = self
|
||||
.first_hop_address
|
||||
.as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN)
|
||||
.into_iter()
|
||||
.chain(self.surb_ack_packet.to_bytes().into_iter())
|
||||
.collect();
|
||||
(self.expected_total_delay, surb_bytes)
|
||||
}
|
||||
|
||||
// partial reciprocal of `prepare_for_sending` performed by the gateway
|
||||
pub fn try_recover_first_hop_packet(
|
||||
b: &[u8],
|
||||
) -> Result<(NymNodeRoutingAddress, SphinxPacket), SURBAckRecoveryError> {
|
||||
if b.len() != Self::len() {
|
||||
Err(SURBAckRecoveryError::InvalidPacketSize)
|
||||
} else {
|
||||
let address = match NymNodeRoutingAddress::try_from_bytes(&b) {
|
||||
Ok(address) => address,
|
||||
Err(_) => return Err(SURBAckRecoveryError::InvalidAddress),
|
||||
};
|
||||
|
||||
// TODO: this will be variable once/if we decide to introduce optimization described
|
||||
// in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size()
|
||||
let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let packet = match SphinxPacket::from_bytes(&b[address_offset..]) {
|
||||
Ok(packet) => packet,
|
||||
Err(_) => return Err(SURBAckRecoveryError::InvalidSphinxPacket),
|
||||
};
|
||||
|
||||
Ok((address, packet))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "nymsphinx-addressing"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nymsphinx-types = { path = "../types" }
|
||||
+3
-3
@@ -4,15 +4,15 @@
|
||||
// of a helper/utils structure, because before it reaches the gateway
|
||||
// it's already destructed).
|
||||
|
||||
use crate::{
|
||||
use nymsphinx_types::{
|
||||
DestinationAddressBytes, NodeAddressBytes, DESTINATION_ADDRESS_LENGTH, NODE_ADDRESS_LENGTH,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RecipientFormattingError;
|
||||
|
||||
impl From<crate::Error> for RecipientFormattingError {
|
||||
fn from(_: crate::Error) -> Self {
|
||||
impl From<nymsphinx_types::Error> for RecipientFormattingError {
|
||||
fn from(_: nymsphinx_types::Error) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
+23
-7
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
use nymsphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
@@ -21,6 +21,10 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
/// Currently, that routing information is an IP address, but in principle it can be anything
|
||||
/// for as long as it's going to fit in the field.
|
||||
|
||||
/// MAX_UNPADDED_LEN represents maximum length an unpadded address could have.
|
||||
/// In this case it's an ipv6 socket address (with version prefix)
|
||||
pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NymNodeRoutingAddressError {
|
||||
InsufficientNumberOfBytesAvailableError,
|
||||
@@ -59,6 +63,23 @@ impl NymNodeRoutingAddress {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts self into a vector of bytes optionally padded with zeroes to the `expected_len`.
|
||||
/// Note this does not necessarily represent a NodeAddressBytes, unless
|
||||
/// `expected_len` == NODE_ADDRESS_LENGTH
|
||||
pub fn as_zero_padded_bytes(&self, expected_len: usize) -> Vec<u8> {
|
||||
let self_bytes = self.as_bytes();
|
||||
if self_bytes.len() >= expected_len {
|
||||
// can't add padding
|
||||
self_bytes
|
||||
} else {
|
||||
self_bytes
|
||||
.into_iter()
|
||||
.chain(std::iter::repeat(0))
|
||||
.take(expected_len)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to recover `Self` from a bytes slice.
|
||||
/// Does not care if it's zero-padded or not.
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Self, NymNodeRoutingAddressError> {
|
||||
@@ -129,12 +150,7 @@ impl TryInto<NodeAddressBytes> for NymNodeRoutingAddress {
|
||||
return Err(NymNodeRoutingAddressError::InsufficientNumberOfBytesAvailableError);
|
||||
}
|
||||
|
||||
let unpadded_address = self.as_bytes();
|
||||
let padded_address: Vec<_> = unpadded_address
|
||||
.into_iter()
|
||||
.chain(std::iter::repeat(0))
|
||||
.take(NODE_ADDRESS_LENGTH)
|
||||
.collect();
|
||||
let padded_address = self.as_zero_padded_bytes(NODE_ADDRESS_LENGTH);
|
||||
|
||||
let mut node_address_bytes = [0u8; 32];
|
||||
node_address_bytes.copy_from_slice(&padded_address);
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nymsphinx-chunking"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
|
||||
nymsphinx-acknowledgements = { path = "../acknowledgements" }
|
||||
nymsphinx-addressing = { path = "../addressing" }
|
||||
nymsphinx-params = { path = "../params" }
|
||||
nymsphinx-types = { path = "../types" }
|
||||
topology = { path = "../../topology" }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,240 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::fragment::{Fragment, FragmentIdentifier};
|
||||
use crate::set::split_into_sets;
|
||||
use nymsphinx_acknowledgements::identifier::AckAes128Key;
|
||||
use nymsphinx_acknowledgements::surb_ack::SURBAck;
|
||||
use nymsphinx_addressing::clients::Recipient;
|
||||
use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use nymsphinx_types::builder::SphinxPacketBuilder;
|
||||
use nymsphinx_types::{delays, Delay, Destination, SphinxPacket};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use topology::{NymTopology, NymTopologyError};
|
||||
|
||||
// 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
|
||||
// have assumed could be possible. A better approach would be to research some Fuzz testing
|
||||
// library like: https://github.com/rust-fuzz/afl.rs and use that instead for the inputs.
|
||||
|
||||
// perhaps it might be useful down the line for interaction testing between client,mixes,etc?
|
||||
|
||||
pub mod fragment;
|
||||
pub mod reconstruction;
|
||||
pub mod set;
|
||||
|
||||
type DefaultRng = OsRng;
|
||||
const DEFAULT_RNG: DefaultRng = OsRng;
|
||||
|
||||
/// The idea behind the process of chunking is to incur as little data overhead as possible due
|
||||
/// to very computationally costly sphinx encapsulation procedure.
|
||||
///
|
||||
/// To achieve this, the underlying message is split into so-called "sets", which are further
|
||||
/// subdivided into the base unit of "fragment" that is directly encapsulated by a Sphinx packet.
|
||||
/// This allows to encapsulate messages of arbitrary length.
|
||||
///
|
||||
/// Each message, regardless of its size, consists of at least a single `Set` that has at least
|
||||
/// a single `Fragment`.
|
||||
///
|
||||
/// Each `Fragment` can have variable, yet fully deterministic, length,
|
||||
/// that depends on its position in the set as well as total number of sets. This is further
|
||||
/// explained in `fragment.rs` file.
|
||||
///
|
||||
/// Similarly, each `Set` can have a variable number of `Fragment`s inside. However, that
|
||||
/// value is more restrictive: if it's the last set into which the message was split
|
||||
/// (or implicitly the only one), it has no lower bound on the number of `Fragment`s.
|
||||
/// (Apart from the restriction of containing at least a single one). If the set is located
|
||||
/// somewhere in the middle, *it must be* full. Finally, regardless of its position, it must also be
|
||||
/// true that it contains no more than `u8::max_value()`, i.e. 255 `Fragment`s.
|
||||
/// Again, the reasoning for this is further explained in `set.rs` file. However, you might
|
||||
/// also want to look at `fragment.rs` to understand the full context behind that design choice.
|
||||
///
|
||||
/// Both of those concepts as well as their structures, i.e. `Set` and `Fragment`
|
||||
/// are further explained in the respective files.
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ChunkingError {
|
||||
InvalidPayloadLengthError,
|
||||
TooBigMessageToSplit,
|
||||
MalformedHeaderError,
|
||||
NoValidProvidersError,
|
||||
NoValidRoutesAvailableError,
|
||||
InvalidTopologyError,
|
||||
TooShortFragmentData,
|
||||
MalformedFragmentData,
|
||||
UnexpectedFragmentCount,
|
||||
MalformedFragmentIdentifier,
|
||||
}
|
||||
|
||||
// Note: `Rng` implies `RngCore`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageChunker<R: CryptoRng + Rng> {
|
||||
rng: R,
|
||||
ack_recipient: Recipient,
|
||||
packet_size: PacketSize,
|
||||
reply_surbs: bool,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
}
|
||||
|
||||
impl MessageChunker<DefaultRng> {
|
||||
pub fn new(
|
||||
ack_recipient: Recipient,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
) -> Self {
|
||||
Self::new_with_rng(
|
||||
DEFAULT_RNG,
|
||||
ack_recipient,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_fixture() -> Self {
|
||||
use nymsphinx_types::{DestinationAddressBytes, NodeAddressBytes};
|
||||
|
||||
let empty_address = [0u8; 32];
|
||||
let empty_recipient = Recipient::new(
|
||||
DestinationAddressBytes::from_bytes(empty_address),
|
||||
NodeAddressBytes::from_bytes(empty_address),
|
||||
);
|
||||
Self::new(empty_recipient, Default::default(), Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: CryptoRng + Rng> MessageChunker<R> {
|
||||
pub fn new_with_rng(
|
||||
rng: R,
|
||||
ack_recipient: Recipient,
|
||||
average_packet_delay_duration: Duration,
|
||||
average_ack_delay_duration: Duration,
|
||||
) -> Self {
|
||||
MessageChunker {
|
||||
rng,
|
||||
ack_recipient,
|
||||
packet_size: Default::default(),
|
||||
reply_surbs: false,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_plaintext_size(&self) -> usize {
|
||||
// we need to put first hop's destination alongside the actual ack
|
||||
// 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 available_size = self.packet_size.plaintext_size()
|
||||
- PacketSize::ACKPacket.size()
|
||||
- MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
if self.reply_surbs {
|
||||
// TODO
|
||||
unimplemented!();
|
||||
}
|
||||
available_size
|
||||
}
|
||||
|
||||
pub fn with_reply_surbs(mut self, reply_surbs: bool) -> Self {
|
||||
self.reply_surbs = reply_surbs;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.packet_size = packet_size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Tries to convert this `Fragment` into a `SphinxPacket` that can be sent through the Nym mix-network,
|
||||
/// such that it contains required SURB-ACK.
|
||||
/// This method can fail if the provided network topology is invalid.
|
||||
/// It returns total expected delay as well as the `SphinxPacket` to be sent through the network.
|
||||
pub fn prepare_chunk_for_sending<T: NymTopology>(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &T,
|
||||
ack_key: &AckAes128Key,
|
||||
packet_recipient: &Recipient,
|
||||
) -> Result<(Delay, (SocketAddr, SphinxPacket)), NymTopologyError> {
|
||||
let (ack_delay, surb_bytes) = self
|
||||
.generate_surb_ack(&fragment.fragment_identifier(), topology, ack_key)?
|
||||
.prepare_for_sending();
|
||||
|
||||
// SURB_FIRST_HOP || SURB_ACK || CHUNK_DATA
|
||||
let packet_payload: Vec<_> = surb_bytes
|
||||
.into_iter()
|
||||
.chain(fragment.into_bytes().into_iter())
|
||||
.collect();
|
||||
|
||||
let route = topology.random_route_to_gateway(&packet_recipient.gateway())?;
|
||||
let delays =
|
||||
delays::generate_from_average_duration(route.len(), self.average_packet_delay_duration);
|
||||
let destination = Destination::new(packet_recipient.destination(), Default::default());
|
||||
|
||||
// once merged, that's an easy rng injection point for sphinx packets : )
|
||||
let packet = SphinxPacketBuilder::new()
|
||||
.with_payload_size(self.packet_size.payload_size())
|
||||
.build_packet(packet_payload, &route, &destination, &delays)
|
||||
.unwrap();
|
||||
|
||||
let first_hop_address =
|
||||
NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap();
|
||||
|
||||
Ok((
|
||||
delays.iter().sum::<Delay>() + ack_delay,
|
||||
(first_hop_address.into(), packet),
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_surb_ack<T>(
|
||||
&mut self,
|
||||
fragment_id: &FragmentIdentifier,
|
||||
topology: &T,
|
||||
ack_key: &AckAes128Key,
|
||||
) -> Result<SURBAck, NymTopologyError>
|
||||
where
|
||||
T: NymTopology,
|
||||
{
|
||||
SURBAck::construct(
|
||||
&mut self.rng,
|
||||
&self.ack_recipient,
|
||||
ack_key,
|
||||
&fragment_id.to_bytes(),
|
||||
self.average_ack_delay_duration,
|
||||
topology,
|
||||
)
|
||||
}
|
||||
|
||||
/// Takes the entire message and splits it into bytes chunks that will fit into sphinx packets
|
||||
/// after attaching SURB-ACK.
|
||||
/// After receiving they can be combined using `reconstruction::MessageReconstructor`
|
||||
/// to obtain the original message back.
|
||||
pub fn split_message(&mut self, message: &[u8]) -> Vec<Fragment> {
|
||||
let available_plaintext_per_fragment = self.available_plaintext_size();
|
||||
|
||||
split_into_sets(&mut self.rng, message, available_plaintext_per_fragment)
|
||||
.into_iter()
|
||||
.flat_map(|fragment_set| fragment_set.into_iter())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
+808
-239
File diff suppressed because it is too large
Load Diff
@@ -12,18 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::chunking::fragment::{
|
||||
Fragment, LINKED_FRAGMENTED_HEADER_LEN, LINKED_FRAGMENTED_PAYLOAD_MAX_LEN,
|
||||
UNFRAGMENTED_PAYLOAD_MAX_LEN, UNLINKED_FRAGMENTED_HEADER_LEN,
|
||||
UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN,
|
||||
use crate::fragment::{
|
||||
linked_fragment_payload_max_len, unlinked_fragment_payload_max_len, Fragment,
|
||||
LINKED_FRAGMENTED_HEADER_LEN, UNLINKED_FRAGMENTED_HEADER_LEN,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
/// In the simplest case of message being divided into a single set, the set has the upper bound
|
||||
/// on its payload length of the maximum number of `Fragment`s multiplied by their maximum,
|
||||
/// fragmented, length.
|
||||
pub const MAX_UNLINKED_SET_PAYLOAD_LENGTH: usize =
|
||||
u8::max_value() as usize * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN;
|
||||
pub const fn max_unlinked_set_payload_length(max_plaintext_size: usize) -> usize {
|
||||
u8::max_value() as usize * unlinked_fragment_payload_max_len(max_plaintext_size)
|
||||
}
|
||||
|
||||
/// If the set is being linked to another one, by either being the very first set, or the very last,
|
||||
/// one of its `Fragment`s must be changed from "unlinked" into "linked" to compensate for a tiny
|
||||
@@ -31,16 +31,20 @@ pub const MAX_UNLINKED_SET_PAYLOAD_LENGTH: usize =
|
||||
/// Note that the "MAX" prefix only applies to if the set is the last one as it does not have
|
||||
/// a lower bound on its length. If the set is one way linked and a first one, it *must have*
|
||||
/// this exact payload length instead.
|
||||
pub const MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH: usize = MAX_UNLINKED_SET_PAYLOAD_LENGTH
|
||||
- (LINKED_FRAGMENTED_HEADER_LEN - UNLINKED_FRAGMENTED_HEADER_LEN);
|
||||
pub const fn max_one_way_linked_set_payload_length(max_plaintext_size: usize) -> usize {
|
||||
max_unlinked_set_payload_length(max_plaintext_size)
|
||||
- (LINKED_FRAGMENTED_HEADER_LEN - UNLINKED_FRAGMENTED_HEADER_LEN)
|
||||
}
|
||||
|
||||
/// If the set is being linked two others sets by being stuck in the middle of divided message,
|
||||
/// two of its `Fragment`s (first and final one) must be changed from
|
||||
/// "unlinked" into "linked" to compensate for data overhead.
|
||||
/// Note that this constant no longer has a "MAX" prefix, this is because each set being stuck
|
||||
/// between different sets, *must* have this exact payload length.
|
||||
pub const TWO_WAY_LINKED_SET_PAYLOAD_LENGTH: usize = MAX_UNLINKED_SET_PAYLOAD_LENGTH
|
||||
- 2 * (LINKED_FRAGMENTED_HEADER_LEN - UNLINKED_FRAGMENTED_HEADER_LEN);
|
||||
pub const fn two_way_linked_set_payload_length(max_plaintext_size: usize) -> usize {
|
||||
max_unlinked_set_payload_length(max_plaintext_size)
|
||||
- 2 * (LINKED_FRAGMENTED_HEADER_LEN - UNLINKED_FRAGMENTED_HEADER_LEN)
|
||||
}
|
||||
|
||||
/// `FragmentSet` is an ordered collection of 1 to 255 `Fragment`s, each with the same ID
|
||||
/// that can be used to produce original message, assuming no linking took place.
|
||||
@@ -69,7 +73,7 @@ pub(crate) type FragmentSet = Vec<Fragment>;
|
||||
/// This approach saves whole byte per `Fragment`, which while may seem insignificant and
|
||||
/// introduces extra complexity, quickly adds up when faced with sphinx packet encapsulation for longer
|
||||
/// messages.
|
||||
/// Finally, the reason 0 id is not allowed is to explicitly distinguish it from unfragmented
|
||||
/// Finally, the reason 0 id is not allowed is to explicitly distinguish it from `COVER_FRAG_ID`
|
||||
/// `Fragment`s thus allowing for some additional optimizations by letting it skip
|
||||
/// certain procedures when reconstructing.
|
||||
fn generate_set_id<R: Rng>(rng: &mut R) -> i32 {
|
||||
@@ -83,18 +87,16 @@ fn generate_set_id<R: Rng>(rng: &mut R) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// The simplest case of splitting underlying message - when it fits into a single
|
||||
/// `Fragment` thus requiring no linking or even a set id.
|
||||
/// For obvious reasons the most efficient approach.
|
||||
fn prepare_unfragmented_set(message: &[u8]) -> FragmentSet {
|
||||
vec![Fragment::try_new_unfragmented(&message).unwrap()]
|
||||
}
|
||||
|
||||
/// Splits underlying message into multiple `Fragment`s while all of them fit in a single
|
||||
/// `Set` (number of `Fragment`s <= 255)
|
||||
fn prepare_unlinked_fragmented_set(message: &[u8], id: i32) -> FragmentSet {
|
||||
let pre_casted_frags =
|
||||
(message.len() as f64 / UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN as f64).ceil() as usize;
|
||||
fn prepare_unlinked_fragmented_set(
|
||||
message: &[u8],
|
||||
id: i32,
|
||||
max_plaintext_size: usize,
|
||||
) -> FragmentSet {
|
||||
let pre_casted_frags = (message.len() as f64
|
||||
/ unlinked_fragment_payload_max_len(max_plaintext_size) as f64)
|
||||
.ceil() as usize;
|
||||
|
||||
debug_assert!(pre_casted_frags <= u8::max_value() as usize);
|
||||
let num_fragments = pre_casted_frags as u8;
|
||||
@@ -103,14 +105,22 @@ fn prepare_unlinked_fragmented_set(message: &[u8], id: i32) -> FragmentSet {
|
||||
|
||||
for i in 1..(pre_casted_frags + 1) {
|
||||
// we can't use u8 directly here as upper (NON-INCLUSIVE, so it would always fit) bound could be u8::max_value() + 1
|
||||
let lb = (i as usize - 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN;
|
||||
let lb = (i as usize - 1) * unlinked_fragment_payload_max_len(max_plaintext_size);
|
||||
let ub = usize::min(
|
||||
message.len(),
|
||||
i as usize * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN,
|
||||
i as usize * unlinked_fragment_payload_max_len(max_plaintext_size),
|
||||
);
|
||||
fragments.push(
|
||||
Fragment::try_new_fragmented(&message[lb..ub], id, num_fragments, i as u8, None, None)
|
||||
.unwrap(),
|
||||
Fragment::try_new(
|
||||
&message[lb..ub],
|
||||
id,
|
||||
num_fragments,
|
||||
i as u8,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,19 +136,21 @@ fn prepare_linked_fragment_set(
|
||||
id: i32,
|
||||
previous_link_id: Option<i32>,
|
||||
next_link_id: Option<i32>,
|
||||
max_plaintext_size: usize,
|
||||
) -> FragmentSet {
|
||||
// determine number of fragments in the set:
|
||||
let num_frags_usize = if next_link_id.is_some() {
|
||||
u8::max_value() as usize
|
||||
} else {
|
||||
// we know this set is linked, if it's not post-linked then it MUST BE pre-linked
|
||||
let tail_len = if message.len() >= LINKED_FRAGMENTED_PAYLOAD_MAX_LEN {
|
||||
message.len() - LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
let tail_len = if message.len() >= linked_fragment_payload_max_len(max_plaintext_size) {
|
||||
message.len() - linked_fragment_payload_max_len(max_plaintext_size)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let pre_casted_frags =
|
||||
1 + (tail_len as f64 / UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN as f64).ceil() as usize;
|
||||
let pre_casted_frags = 1
|
||||
+ (tail_len as f64 / unlinked_fragment_payload_max_len(max_plaintext_size) as f64)
|
||||
.ceil() as usize;
|
||||
if pre_casted_frags > u8::max_value() as usize {
|
||||
panic!("message would produce too many fragments!")
|
||||
};
|
||||
@@ -148,16 +160,19 @@ fn prepare_linked_fragment_set(
|
||||
// determine bounds for the first fragment which depends on whether set is pre-linked
|
||||
let mut lb = 0;
|
||||
let mut ub = if previous_link_id.is_some() {
|
||||
usize::min(message.len(), LINKED_FRAGMENTED_PAYLOAD_MAX_LEN)
|
||||
usize::min(
|
||||
message.len(),
|
||||
linked_fragment_payload_max_len(max_plaintext_size),
|
||||
)
|
||||
} else {
|
||||
// the set might be linked, but fragment itself is not (i.e. the set is linked at the tail)
|
||||
UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
unlinked_fragment_payload_max_len(max_plaintext_size)
|
||||
};
|
||||
|
||||
let mut fragments = Vec::with_capacity(num_frags_usize);
|
||||
for i in 1..(num_frags_usize + 1) {
|
||||
// we can't use u8 directly here as upper (NON-INCLUSIVE, so i would always fit) bound could be u8::max_value() + 1
|
||||
let fragment = Fragment::try_new_fragmented(
|
||||
let fragment = Fragment::try_new(
|
||||
&message[lb..ub],
|
||||
id,
|
||||
num_frags_usize as u8,
|
||||
@@ -168,30 +183,37 @@ fn prepare_linked_fragment_set(
|
||||
} else {
|
||||
None
|
||||
},
|
||||
max_plaintext_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
fragments.push(fragment);
|
||||
// update bounds for the next fragment
|
||||
lb = ub;
|
||||
ub = usize::min(message.len(), ub + UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN);
|
||||
ub = usize::min(
|
||||
message.len(),
|
||||
ub + unlinked_fragment_payload_max_len(max_plaintext_size),
|
||||
);
|
||||
}
|
||||
|
||||
fragments
|
||||
}
|
||||
|
||||
/// Based on total message length, determines the number of sets into which it is going to be split.
|
||||
fn total_number_of_sets(message_len: usize) -> usize {
|
||||
if message_len <= MAX_UNLINKED_SET_PAYLOAD_LENGTH {
|
||||
fn total_number_of_sets(message_len: usize, max_plaintext_size: usize) -> usize {
|
||||
if message_len <= max_unlinked_set_payload_length(max_plaintext_size) {
|
||||
1
|
||||
} else if message_len > MAX_UNLINKED_SET_PAYLOAD_LENGTH
|
||||
&& message_len <= 2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
} else if message_len > max_unlinked_set_payload_length(max_plaintext_size)
|
||||
&& message_len <= 2 * max_one_way_linked_set_payload_length(max_plaintext_size)
|
||||
{
|
||||
2
|
||||
} else {
|
||||
let len_without_edges = message_len - 2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH;
|
||||
let len_without_edges =
|
||||
message_len - 2 * max_one_way_linked_set_payload_length(max_plaintext_size);
|
||||
// every set in between edges must be two way linked
|
||||
(len_without_edges as f64 / TWO_WAY_LINKED_SET_PAYLOAD_LENGTH as f64).ceil() as usize + 2
|
||||
(len_without_edges as f64 / two_way_linked_set_payload_length(max_plaintext_size) as f64)
|
||||
.ceil() as usize
|
||||
+ 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,38 +224,50 @@ fn prepare_fragment_set(
|
||||
id: i32,
|
||||
previous_link_id: Option<i32>,
|
||||
next_link_id: Option<i32>,
|
||||
max_plaintext_size: usize,
|
||||
) -> FragmentSet {
|
||||
if previous_link_id.is_some() || next_link_id.is_some() {
|
||||
prepare_linked_fragment_set(message, id, previous_link_id, next_link_id)
|
||||
} else if message.len() > UNFRAGMENTED_PAYLOAD_MAX_LEN {
|
||||
prepare_linked_fragment_set(
|
||||
message,
|
||||
id,
|
||||
previous_link_id,
|
||||
next_link_id,
|
||||
max_plaintext_size,
|
||||
)
|
||||
} else {
|
||||
// the bounds on whether the message fits in an unlinked set should have been done by the callee
|
||||
// when determining ids of other sets
|
||||
prepare_unlinked_fragmented_set(message, id)
|
||||
} else {
|
||||
prepare_unfragmented_set(message)
|
||||
prepare_unlinked_fragmented_set(message, id, max_plaintext_size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point for splitting whole message into possibly multiple `Set`s.
|
||||
pub(crate) fn split_into_sets(message: &[u8]) -> Vec<FragmentSet> {
|
||||
use rand::thread_rng;
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let num_of_sets = total_number_of_sets(message.len());
|
||||
pub(crate) fn split_into_sets<R: Rng>(
|
||||
rng: &mut R,
|
||||
message: &[u8],
|
||||
max_plaintext_size: usize,
|
||||
) -> Vec<FragmentSet> {
|
||||
let num_of_sets = total_number_of_sets(message.len(), max_plaintext_size);
|
||||
if num_of_sets == 1 {
|
||||
let set_id = generate_set_id(&mut rng);
|
||||
vec![prepare_fragment_set(message, set_id, None, None)]
|
||||
let set_id = generate_set_id(rng);
|
||||
vec![prepare_fragment_set(
|
||||
message,
|
||||
set_id,
|
||||
None,
|
||||
None,
|
||||
max_plaintext_size,
|
||||
)]
|
||||
} else {
|
||||
let mut sets = Vec::with_capacity(num_of_sets);
|
||||
// pre-generate all ids for the sets
|
||||
let set_ids: Vec<_> = std::iter::repeat(())
|
||||
.map(|_| generate_set_id(&mut rng))
|
||||
.map(|_| generate_set_id(rng))
|
||||
.take(num_of_sets)
|
||||
.collect();
|
||||
|
||||
// initial bounds for the set payloads
|
||||
let mut lb = 0;
|
||||
let mut ub = MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH;
|
||||
let mut ub = max_one_way_linked_set_payload_length(max_plaintext_size);
|
||||
|
||||
for i in 0..num_of_sets {
|
||||
let fragment_set = prepare_fragment_set(
|
||||
@@ -245,6 +279,7 @@ pub(crate) fn split_into_sets(message: &[u8]) -> Vec<FragmentSet> {
|
||||
} else {
|
||||
Some(set_ids[i + 1])
|
||||
},
|
||||
max_plaintext_size,
|
||||
);
|
||||
|
||||
sets.push(fragment_set);
|
||||
@@ -252,9 +287,15 @@ pub(crate) fn split_into_sets(message: &[u8]) -> Vec<FragmentSet> {
|
||||
lb = ub;
|
||||
ub = if i == num_of_sets - 2 {
|
||||
// we're going to go into the last iteration now, hence the last set will be one-way linked
|
||||
usize::min(message.len(), ub + MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH)
|
||||
usize::min(
|
||||
message.len(),
|
||||
ub + max_one_way_linked_set_payload_length(max_plaintext_size),
|
||||
)
|
||||
} else {
|
||||
usize::min(message.len(), ub + TWO_WAY_LINKED_SET_PAYLOAD_LENGTH)
|
||||
usize::min(
|
||||
message.len(),
|
||||
ub + two_way_linked_set_payload_length(max_plaintext_size),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,13 +307,21 @@ pub(crate) fn split_into_sets(message: &[u8]) -> Vec<FragmentSet> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
|
||||
fn max_plaintext_size() -> usize {
|
||||
PacketSize::default().plaintext_size() - PacketSize::ACKPacket.size()
|
||||
}
|
||||
|
||||
fn verify_unlinked_set_payload(mut set: FragmentSet, payload: &[u8]) {
|
||||
for i in (0..set.len()).rev() {
|
||||
assert_eq!(
|
||||
set.pop().unwrap().extract_payload(),
|
||||
payload[i * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
..usize::min(payload.len(), (i + 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN)]
|
||||
payload[i * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
..usize::min(
|
||||
payload.len(),
|
||||
(i + 1) * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
)]
|
||||
.to_vec()
|
||||
)
|
||||
}
|
||||
@@ -283,11 +332,13 @@ mod tests {
|
||||
let lb = if i == 0 {
|
||||
0
|
||||
} else {
|
||||
(i - 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
(i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ linked_fragment_payload_max_len(max_plaintext_size())
|
||||
};
|
||||
let ub = usize::min(
|
||||
payload.len(),
|
||||
i * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + LINKED_FRAGMENTED_PAYLOAD_MAX_LEN,
|
||||
i * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ linked_fragment_payload_max_len(max_plaintext_size()),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -299,11 +350,12 @@ mod tests {
|
||||
|
||||
fn verify_post_linked_set_payload(mut set: FragmentSet, payload: &[u8]) {
|
||||
for i in (0..set.len()).rev() {
|
||||
let lb = i * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN;
|
||||
let lb = i * unlinked_fragment_payload_max_len(max_plaintext_size());
|
||||
let ub = if i == (u8::max_value() as usize - 1) {
|
||||
i * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
i * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ linked_fragment_payload_max_len(max_plaintext_size())
|
||||
} else {
|
||||
(i + 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
(i + 1) * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
@@ -318,13 +370,15 @@ mod tests {
|
||||
let lb = if i == 0 {
|
||||
0
|
||||
} else {
|
||||
(i - 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
(i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ linked_fragment_payload_max_len(max_plaintext_size())
|
||||
};
|
||||
let ub = if i == (u8::max_value() as usize - 1) {
|
||||
(i - 1) * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
+ 2 * LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
(i - 1) * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ 2 * linked_fragment_payload_max_len(max_plaintext_size())
|
||||
} else {
|
||||
i * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
i * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ linked_fragment_payload_max_len(max_plaintext_size())
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
@@ -334,7 +388,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn verfiy_correct_link(left: &FragmentSet, right: &FragmentSet) {
|
||||
fn verify_correct_link(left: &FragmentSet, right: &FragmentSet) {
|
||||
let first_id = left[0].id();
|
||||
let post_id = left[254].next_fragments_set_id().unwrap();
|
||||
|
||||
@@ -345,31 +399,6 @@ mod tests {
|
||||
assert_eq!(second_id, post_id);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod preparing_unfragmented_set {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn makes_set_with_single_unfragmented_element_for_valid_message_lengths() {
|
||||
let mut set = prepare_unfragmented_set(&[1]);
|
||||
assert_eq!(1, set.len());
|
||||
assert_eq!(set.pop().unwrap().extract_payload(), [1].to_vec());
|
||||
|
||||
let mut set = prepare_unfragmented_set(&[1u8; UNFRAGMENTED_PAYLOAD_MAX_LEN]);
|
||||
assert_eq!(1, set.len());
|
||||
assert_eq!(
|
||||
set.pop().unwrap().extract_payload(),
|
||||
[1u8; UNFRAGMENTED_PAYLOAD_MAX_LEN].to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_for_too_long_payload() {
|
||||
prepare_unfragmented_set(&[1u8; UNFRAGMENTED_PAYLOAD_MAX_LEN + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod preparing_unlinked_set {
|
||||
// remember this this is only called for a sole set with <= 255 fragments
|
||||
@@ -381,30 +410,46 @@ mod tests {
|
||||
let id = 12345;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut two_element_set_payload = [0u8; UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + 1];
|
||||
let mut two_element_set_payload =
|
||||
vec![0u8; unlinked_fragment_payload_max_len(max_plaintext_size()) + 1];
|
||||
rng.fill_bytes(&mut two_element_set_payload);
|
||||
let two_element_set = prepare_unlinked_fragmented_set(&two_element_set_payload, id);
|
||||
let two_element_set =
|
||||
prepare_unlinked_fragmented_set(&two_element_set_payload, id, max_plaintext_size());
|
||||
assert_eq!(2, two_element_set.len());
|
||||
verify_unlinked_set_payload(two_element_set, &two_element_set_payload);
|
||||
|
||||
let mut forty_two_element_set_payload =
|
||||
[0u8; 41 * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + 42];
|
||||
vec![0u8; 41 * unlinked_fragment_payload_max_len(max_plaintext_size()) + 42];
|
||||
rng.fill_bytes(&mut forty_two_element_set_payload);
|
||||
let forty_two_element_set =
|
||||
prepare_unlinked_fragmented_set(&forty_two_element_set_payload, id);
|
||||
let forty_two_element_set = prepare_unlinked_fragmented_set(
|
||||
&forty_two_element_set_payload,
|
||||
id,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(42, forty_two_element_set.len());
|
||||
verify_unlinked_set_payload(forty_two_element_set, &forty_two_element_set_payload);
|
||||
|
||||
let mut max_fragments_set_payload =
|
||||
[0u8; MAX_UNLINKED_SET_PAYLOAD_LENGTH - UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN + 1]; // last fragment should have a single byte of data
|
||||
vec![
|
||||
0u8;
|
||||
max_unlinked_set_payload_length(max_plaintext_size())
|
||||
- unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ 1
|
||||
]; // last fragment should have a single byte of data
|
||||
rng.fill_bytes(&mut max_fragments_set_payload);
|
||||
let max_fragment_set = prepare_unlinked_fragmented_set(&max_fragments_set_payload, id);
|
||||
let max_fragment_set = prepare_unlinked_fragmented_set(
|
||||
&max_fragments_set_payload,
|
||||
id,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(u8::max_value() as usize, max_fragment_set.len());
|
||||
verify_unlinked_set_payload(max_fragment_set, &max_fragments_set_payload);
|
||||
|
||||
let mut full_set_payload = [0u8; MAX_UNLINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut full_set_payload =
|
||||
vec![0u8; max_unlinked_set_payload_length(max_plaintext_size())];
|
||||
rng.fill_bytes(&mut full_set_payload);
|
||||
let full_fragment_set = prepare_unlinked_fragmented_set(&full_set_payload, id);
|
||||
let full_fragment_set =
|
||||
prepare_unlinked_fragmented_set(&full_set_payload, id, max_plaintext_size());
|
||||
assert_eq!(u8::max_value() as usize, full_fragment_set.len());
|
||||
verify_unlinked_set_payload(full_fragment_set, &full_set_payload);
|
||||
}
|
||||
@@ -412,7 +457,11 @@ mod tests {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_for_too_long_payload() {
|
||||
prepare_unlinked_fragmented_set(&[0u8; MAX_UNLINKED_SET_PAYLOAD_LENGTH + 1], 12345);
|
||||
prepare_unlinked_fragmented_set(
|
||||
&vec![0u8; max_unlinked_set_payload_length(max_plaintext_size()) + 1],
|
||||
12345,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,39 +476,66 @@ mod tests {
|
||||
let link_id = 1234;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut two_element_set_payload = [0u8; LINKED_FRAGMENTED_PAYLOAD_MAX_LEN + 1];
|
||||
let mut two_element_set_payload =
|
||||
vec![0u8; linked_fragment_payload_max_len(max_plaintext_size()) + 1];
|
||||
rng.fill_bytes(&mut two_element_set_payload);
|
||||
let two_element_set =
|
||||
prepare_linked_fragment_set(&two_element_set_payload, id, Some(link_id), None);
|
||||
let two_element_set = prepare_linked_fragment_set(
|
||||
&two_element_set_payload,
|
||||
id,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(2, two_element_set.len());
|
||||
verify_pre_linked_set_payload(two_element_set, &two_element_set_payload);
|
||||
|
||||
let mut forty_two_element_set_payload = [0u8; LINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
+ 40 * UNLINKED_FRAGMENTED_PAYLOAD_MAX_LEN
|
||||
+ 42];
|
||||
let mut forty_two_element_set_payload =
|
||||
vec![
|
||||
0u8;
|
||||
linked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ 40 * unlinked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ 42
|
||||
];
|
||||
rng.fill_bytes(&mut forty_two_element_set_payload);
|
||||
let forty_two_element_set = prepare_linked_fragment_set(
|
||||
&forty_two_element_set_payload,
|
||||
id,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(42, forty_two_element_set.len());
|
||||
verify_pre_linked_set_payload(forty_two_element_set, &forty_two_element_set_payload);
|
||||
|
||||
let mut max_fragments_set_payload =
|
||||
[0u8; MAX_UNLINKED_SET_PAYLOAD_LENGTH - LINKED_FRAGMENTED_PAYLOAD_MAX_LEN + 1]; // last fragment should have a single byte of data
|
||||
vec![
|
||||
0u8;
|
||||
max_unlinked_set_payload_length(max_plaintext_size())
|
||||
- linked_fragment_payload_max_len(max_plaintext_size())
|
||||
+ 1
|
||||
]; // last fragment should have a single byte of data
|
||||
rng.fill_bytes(&mut max_fragments_set_payload);
|
||||
|
||||
let max_fragment_set =
|
||||
prepare_linked_fragment_set(&max_fragments_set_payload, id, Some(link_id), None);
|
||||
let max_fragment_set = prepare_linked_fragment_set(
|
||||
&max_fragments_set_payload,
|
||||
id,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(u8::max_value() as usize, max_fragment_set.len());
|
||||
verify_pre_linked_set_payload(max_fragment_set, &max_fragments_set_payload);
|
||||
|
||||
let mut full_set_payload = [0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut full_set_payload =
|
||||
vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size())];
|
||||
rng.fill_bytes(&mut full_set_payload);
|
||||
let full_fragment_set =
|
||||
prepare_linked_fragment_set(&full_set_payload, id, Some(link_id), None);
|
||||
let full_fragment_set = prepare_linked_fragment_set(
|
||||
&full_set_payload,
|
||||
id,
|
||||
Some(link_id),
|
||||
None,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(u8::max_value() as usize, full_fragment_set.len());
|
||||
verify_pre_linked_set_payload(full_fragment_set, &full_set_payload);
|
||||
}
|
||||
@@ -468,10 +544,11 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn panics_for_too_long_payload_for_pre_linked_set() {
|
||||
prepare_linked_fragment_set(
|
||||
&[0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + 1],
|
||||
&vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size()) + 1],
|
||||
12345,
|
||||
Some(1234),
|
||||
None,
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -482,10 +559,16 @@ mod tests {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// if set is post-linked, there is only a single valid case - full length payload
|
||||
let mut full_set_payload = [0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut full_set_payload =
|
||||
vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size())];
|
||||
rng.fill_bytes(&mut full_set_payload);
|
||||
let full_fragment_set =
|
||||
prepare_linked_fragment_set(&full_set_payload, id, None, Some(link_id));
|
||||
let full_fragment_set = prepare_linked_fragment_set(
|
||||
&full_set_payload,
|
||||
id,
|
||||
None,
|
||||
Some(link_id),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(u8::max_value() as usize, full_fragment_set.len());
|
||||
verify_post_linked_set_payload(full_fragment_set, &full_set_payload);
|
||||
}
|
||||
@@ -494,10 +577,11 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn panics_for_too_long_payload_for_post_linked_set() {
|
||||
prepare_linked_fragment_set(
|
||||
&[0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + 1],
|
||||
&vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size()) + 1],
|
||||
12345,
|
||||
None,
|
||||
Some(1234),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -505,10 +589,11 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn panics_for_too_short_payload_for_post_linked_set() {
|
||||
prepare_linked_fragment_set(
|
||||
&[0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH - 1],
|
||||
&vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size()) - 1],
|
||||
12345,
|
||||
None,
|
||||
Some(1234),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -521,13 +606,15 @@ mod tests {
|
||||
let post_link_id = 123456;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut full_set_payload = [0u8; TWO_WAY_LINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut full_set_payload =
|
||||
vec![0u8; two_way_linked_set_payload_length(max_plaintext_size())];
|
||||
rng.fill_bytes(&mut full_set_payload);
|
||||
let full_fragment_set = prepare_linked_fragment_set(
|
||||
&full_set_payload,
|
||||
id,
|
||||
Some(pre_link_id),
|
||||
Some(post_link_id),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
assert_eq!(u8::max_value() as usize, full_fragment_set.len());
|
||||
verify_two_way_linked_set_payload(full_fragment_set, &full_set_payload);
|
||||
@@ -537,10 +624,11 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn panics_for_too_long_payload_for_two_way_linked_set() {
|
||||
prepare_linked_fragment_set(
|
||||
&[0u8; TWO_WAY_LINKED_SET_PAYLOAD_LENGTH + 1],
|
||||
&vec![0u8; two_way_linked_set_payload_length(max_plaintext_size()) + 1],
|
||||
12345,
|
||||
Some(123456),
|
||||
Some(1234),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -548,10 +636,11 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn panics_for_too_short_payload_for_two_way_linked_set() {
|
||||
prepare_linked_fragment_set(
|
||||
&[0u8; TWO_WAY_LINKED_SET_PAYLOAD_LENGTH - 1],
|
||||
&vec![0u8; two_way_linked_set_payload_length(max_plaintext_size()) - 1],
|
||||
12345,
|
||||
Some(123456),
|
||||
Some(1234),
|
||||
max_plaintext_size(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -561,37 +650,14 @@ mod tests {
|
||||
use super::*;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[test]
|
||||
fn correctly_creates_set_with_single_unfragmented_element_when_expected() {
|
||||
let mut rng = thread_rng();
|
||||
let tiny_message = [1, 2, 3, 4, 5];
|
||||
let mut max_unfragmented_message = [0u8; UNFRAGMENTED_PAYLOAD_MAX_LEN];
|
||||
rng.fill_bytes(&mut max_unfragmented_message);
|
||||
|
||||
let mut sets = split_into_sets(&tiny_message);
|
||||
assert_eq!(1, sets.len());
|
||||
assert_eq!(1, sets[0].len());
|
||||
assert_eq!(
|
||||
tiny_message.to_vec(),
|
||||
sets.pop().unwrap().pop().unwrap().extract_payload()
|
||||
);
|
||||
|
||||
let mut sets = split_into_sets(&max_unfragmented_message);
|
||||
assert_eq!(1, sets.len());
|
||||
assert_eq!(1, sets[0].len());
|
||||
assert_eq!(
|
||||
max_unfragmented_message.to_vec(),
|
||||
sets.pop().unwrap().pop().unwrap().extract_payload()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_creates_single_fragmented_set_when_expected() {
|
||||
let mut rng = thread_rng();
|
||||
let mut message = [0u8; MAX_UNLINKED_SET_PAYLOAD_LENGTH - 2345];
|
||||
let mut message =
|
||||
vec![0u8; max_unlinked_set_payload_length(max_plaintext_size()) - 2345];
|
||||
rng.fill_bytes(&mut message);
|
||||
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(1, sets.len());
|
||||
verify_unlinked_set_payload(sets.pop().unwrap(), &message);
|
||||
}
|
||||
@@ -602,127 +668,141 @@ mod tests {
|
||||
fn correctly_creates_two_singly_linked_sets_with_second_set_containing_data_fitting_in_unfragmented_payload(
|
||||
) {
|
||||
let mut rng = thread_rng();
|
||||
let mut message = [0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + 123];
|
||||
let mut message =
|
||||
vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size()) + 123];
|
||||
rng.fill_bytes(&mut message);
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(2, sets.len());
|
||||
verfiy_correct_link(&sets[0], &sets[1]);
|
||||
verify_correct_link(&sets[0], &sets[1]);
|
||||
verify_pre_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH..],
|
||||
&message[max_one_way_linked_set_payload_length(max_plaintext_size())..],
|
||||
);
|
||||
verify_post_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[..MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[..max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_creates_two_singly_linked_sets_when_expected() {
|
||||
let mut rng = thread_rng();
|
||||
let mut message = [0u8; MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + 2345];
|
||||
let mut message =
|
||||
vec![0u8; max_one_way_linked_set_payload_length(max_plaintext_size()) + 2345];
|
||||
rng.fill_bytes(&mut message);
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(2, sets.len());
|
||||
verfiy_correct_link(&sets[0], &sets[1]);
|
||||
verify_correct_link(&sets[0], &sets[1]);
|
||||
verify_pre_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH..],
|
||||
&message[max_one_way_linked_set_payload_length(max_plaintext_size())..],
|
||||
);
|
||||
verify_post_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[..MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[..max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
|
||||
let mut message = [0u8; 2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut message =
|
||||
vec![0u8; 2 * max_one_way_linked_set_payload_length(max_plaintext_size())];
|
||||
rng.fill_bytes(&mut message);
|
||||
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(2, sets.len());
|
||||
assert_eq!(sets[0].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[1].len(), u8::max_value() as usize);
|
||||
verfiy_correct_link(&sets[0], &sets[1]);
|
||||
verify_correct_link(&sets[0], &sets[1]);
|
||||
verify_pre_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH..],
|
||||
&message[max_one_way_linked_set_payload_length(max_plaintext_size())..],
|
||||
);
|
||||
|
||||
verify_post_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[..MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[..max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_creates_four_correctly_formed_sets_when_expected() {
|
||||
let mut rng = thread_rng();
|
||||
let mut message = [0u8; 2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 2345];
|
||||
let mut message = vec![
|
||||
0u8;
|
||||
2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 2345
|
||||
];
|
||||
rng.fill_bytes(&mut message);
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(4, sets.len());
|
||||
assert_eq!(sets[0].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[1].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[2].len(), u8::max_value() as usize);
|
||||
|
||||
verfiy_correct_link(&sets[0], &sets[1]);
|
||||
verfiy_correct_link(&sets[1], &sets[2]);
|
||||
verfiy_correct_link(&sets[2], &sets[3]);
|
||||
verify_correct_link(&sets[0], &sets[1]);
|
||||
verify_correct_link(&sets[1], &sets[2]);
|
||||
verify_correct_link(&sets[2], &sets[3]);
|
||||
verify_pre_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH..],
|
||||
&message[2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())..],
|
||||
);
|
||||
verify_two_way_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[TWO_WAY_LINKED_SET_PAYLOAD_LENGTH + MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
..2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
..2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
verify_two_way_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
..TWO_WAY_LINKED_SET_PAYLOAD_LENGTH + MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
..two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
verify_post_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[..MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[..max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
|
||||
let mut message = [0u8; 2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH];
|
||||
let mut message =
|
||||
vec![
|
||||
0u8;
|
||||
2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
];
|
||||
rng.fill_bytes(&mut message);
|
||||
|
||||
let mut sets = split_into_sets(&message);
|
||||
let mut sets = split_into_sets(&mut rng, &message, max_plaintext_size());
|
||||
assert_eq!(4, sets.len());
|
||||
assert_eq!(sets[0].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[1].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[2].len(), u8::max_value() as usize);
|
||||
assert_eq!(sets[3].len(), u8::max_value() as usize);
|
||||
|
||||
verfiy_correct_link(&sets[0], &sets[1]);
|
||||
verfiy_correct_link(&sets[1], &sets[2]);
|
||||
verfiy_correct_link(&sets[2], &sets[3]);
|
||||
verify_correct_link(&sets[0], &sets[1]);
|
||||
verify_correct_link(&sets[1], &sets[2]);
|
||||
verify_correct_link(&sets[2], &sets[3]);
|
||||
verify_pre_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH..],
|
||||
&message[2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())..],
|
||||
);
|
||||
verify_two_way_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[TWO_WAY_LINKED_SET_PAYLOAD_LENGTH + MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
..2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
..2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
verify_two_way_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
..TWO_WAY_LINKED_SET_PAYLOAD_LENGTH + MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
..two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
verify_post_linked_set_payload(
|
||||
sets.pop().unwrap(),
|
||||
&message[..MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH],
|
||||
&message[..max_one_way_linked_set_payload_length(max_plaintext_size())],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -735,66 +815,89 @@ mod tests {
|
||||
fn total_number_of_sets() {
|
||||
assert_eq!(
|
||||
1,
|
||||
super::total_number_of_sets(MAX_UNLINKED_SET_PAYLOAD_LENGTH - 1)
|
||||
super::total_number_of_sets(
|
||||
max_unlinked_set_payload_length(max_plaintext_size()) - 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
1,
|
||||
super::total_number_of_sets(MAX_UNLINKED_SET_PAYLOAD_LENGTH)
|
||||
super::total_number_of_sets(
|
||||
max_unlinked_set_payload_length(max_plaintext_size()),
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
2,
|
||||
super::total_number_of_sets(MAX_UNLINKED_SET_PAYLOAD_LENGTH + 1)
|
||||
super::total_number_of_sets(
|
||||
max_unlinked_set_payload_length(max_plaintext_size()) + 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
2,
|
||||
super::total_number_of_sets(2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH)
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
super::total_number_of_sets(2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + 1)
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
- 1
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size()),
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH + TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size()) + 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
super::total_number_of_sets(
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ two_way_linked_set_payload_length(max_plaintext_size())
|
||||
- 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
super::total_number_of_sets(
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ two_way_linked_set_payload_length(max_plaintext_size()),
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 1
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
- 1
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
- 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 2 * two_way_linked_set_payload_length(max_plaintext_size()),
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
5,
|
||||
super::total_number_of_sets(
|
||||
2 * MAX_ONE_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 2 * TWO_WAY_LINKED_SET_PAYLOAD_LENGTH
|
||||
+ 1
|
||||
2 * max_one_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 2 * two_way_linked_set_payload_length(max_plaintext_size())
|
||||
+ 1,
|
||||
max_plaintext_size()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nymsphinx-cover"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
|
||||
nymsphinx-acknowledgements = { path = "../acknowledgements" }
|
||||
nymsphinx-addressing = { path = "../addressing" }
|
||||
nymsphinx-chunking = { path = "../chunking" }
|
||||
nymsphinx-params = { path = "../params" }
|
||||
nymsphinx-types = { path = "../types" }
|
||||
topology = { path = "../../topology" }
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nymsphinx_acknowledgements::surb_ack::SURBAck;
|
||||
use nymsphinx_acknowledgements::AckAes128Key;
|
||||
use nymsphinx_addressing::clients::Recipient;
|
||||
use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
||||
use nymsphinx_chunking::fragment::COVER_FRAG_ID;
|
||||
use nymsphinx_params::packet_sizes::PacketSize;
|
||||
use nymsphinx_types::builder::SphinxPacketBuilder;
|
||||
use nymsphinx_types::{delays, Destination, Error as SphinxError, SphinxPacket};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::time;
|
||||
use topology::{NymTopology, NymTopologyError};
|
||||
|
||||
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CoverMessageError {
|
||||
NoValidProvidersError,
|
||||
InvalidTopologyError,
|
||||
SphinxError(SphinxError),
|
||||
InvalidFirstMixAddress,
|
||||
}
|
||||
|
||||
impl From<SphinxError> for CoverMessageError {
|
||||
fn from(err: SphinxError) -> Self {
|
||||
CoverMessageError::SphinxError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymNodeRoutingAddressError> for CoverMessageError {
|
||||
fn from(_: NymNodeRoutingAddressError) -> Self {
|
||||
use CoverMessageError::*;
|
||||
InvalidFirstMixAddress
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopologyError> for CoverMessageError {
|
||||
fn from(_: NymTopologyError) -> Self {
|
||||
CoverMessageError::InvalidTopologyError
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_loop_cover_surb_ack<R, T>(
|
||||
rng: &mut R,
|
||||
topology: &T,
|
||||
ack_key: &AckAes128Key,
|
||||
full_address: &Recipient,
|
||||
average_ack_delay: time::Duration,
|
||||
) -> Result<SURBAck, CoverMessageError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
T: NymTopology,
|
||||
{
|
||||
Ok(SURBAck::construct(
|
||||
rng,
|
||||
full_address,
|
||||
ack_key,
|
||||
&COVER_FRAG_ID.to_bytes(),
|
||||
average_ack_delay,
|
||||
topology,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn generate_loop_cover_packet<R, T>(
|
||||
rng: &mut R,
|
||||
topology: &T,
|
||||
ack_key: &AckAes128Key,
|
||||
full_address: &Recipient,
|
||||
average_ack_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
) -> Result<(SocketAddr, SphinxPacket), CoverMessageError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
T: NymTopology,
|
||||
{
|
||||
// we don't care about total ack delay - we will not be retransmitting it anyway
|
||||
let (_, ack_bytes) =
|
||||
generate_loop_cover_surb_ack(rng, topology, ack_key, full_address, average_ack_delay)?
|
||||
.prepare_for_sending();
|
||||
|
||||
let cover_payload: Vec<_> = ack_bytes
|
||||
.into_iter()
|
||||
.chain(LOOP_COVER_MESSAGE_PAYLOAD.into_iter().cloned())
|
||||
.collect();
|
||||
|
||||
let route = topology.random_route_to_gateway(&full_address.gateway())?;
|
||||
let delays = delays::generate_from_average_duration(route.len(), average_packet_delay);
|
||||
// in our design we don't care about SURB_ID
|
||||
let destination = Destination::new(full_address.destination(), Default::default());
|
||||
|
||||
// once merged, that's an easy rng injection point for sphinx packets : )
|
||||
let packet = SphinxPacketBuilder::new()
|
||||
.with_payload_size(PacketSize::default().payload_size())
|
||||
.build_packet(cover_payload, &route, &destination, &delays)
|
||||
.unwrap();
|
||||
|
||||
let first_hop_address =
|
||||
NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap();
|
||||
|
||||
Ok((first_hop_address.into(), packet))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "nymsphinx-framing"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.5"
|
||||
tokio-util = { version = "0.3.1", features = ["codec"] }
|
||||
|
||||
nymsphinx-types = { path = "../types" }
|
||||
nymsphinx-params = { path = "../params" }
|
||||
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::packets::{InvalidPacketSize, PacketSize};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use sphinx::SphinxPacket;
|
||||
use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize};
|
||||
use nymsphinx_types::SphinxPacket;
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "nymsphinx-params"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nymsphinx-types = { path = "../types" }
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod packet_sizes;
|
||||
@@ -12,22 +12,23 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nymsphinx_types::header::HEADER_SIZE;
|
||||
use nymsphinx_types::PAYLOAD_OVERHEAD_SIZE;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// it's up to the smart people to figure those values out : )
|
||||
const REGULAR_PACKET_SIZE: usize = 2 * 1024;
|
||||
const ACK_PACKET_SIZE: usize = 512;
|
||||
const EXTENDED_PACKET_SIZE: usize = 32 * 1024;
|
||||
const REGULAR_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 2 * 1024;
|
||||
const ACK_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 24;
|
||||
const EXTENDED_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024;
|
||||
|
||||
pub struct InvalidPacketSize;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PacketSize {
|
||||
RegularPacket = 1, // for example instant messaging use case
|
||||
ACKPacket = 2, // for sending SURB-ACKs
|
||||
ExtendedPacket = 3, // for example for streaming fast and furious in uncompressed 10bit 4K HDR quality
|
||||
|
||||
PreSURBChanges = 0,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PacketSize {
|
||||
@@ -38,7 +39,6 @@ impl TryFrom<u8> for PacketSize {
|
||||
_ if value == (PacketSize::RegularPacket as u8) => Ok(Self::RegularPacket),
|
||||
_ if value == (PacketSize::ACKPacket as u8) => Ok(Self::ACKPacket),
|
||||
_ if value == (PacketSize::ExtendedPacket as u8) => Ok(Self::ExtendedPacket),
|
||||
_ if value == (PacketSize::PreSURBChanges as u8) => Ok(Self::PreSURBChanges),
|
||||
_ => Err(InvalidPacketSize),
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,17 @@ impl PacketSize {
|
||||
PacketSize::RegularPacket => REGULAR_PACKET_SIZE,
|
||||
PacketSize::ACKPacket => ACK_PACKET_SIZE,
|
||||
PacketSize::ExtendedPacket => EXTENDED_PACKET_SIZE,
|
||||
PacketSize::PreSURBChanges => crate::PACKET_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plaintext_size(&self) -> usize {
|
||||
self.size() - HEADER_SIZE - PAYLOAD_OVERHEAD_SIZE
|
||||
}
|
||||
|
||||
pub fn payload_size(&self) -> usize {
|
||||
self.size() - HEADER_SIZE
|
||||
}
|
||||
|
||||
pub fn get_type(size: usize) -> std::result::Result<Self, InvalidPacketSize> {
|
||||
if PacketSize::RegularPacket.size() == size {
|
||||
Ok(PacketSize::RegularPacket)
|
||||
@@ -61,8 +68,6 @@ impl PacketSize {
|
||||
Ok(PacketSize::ACKPacket)
|
||||
} else if PacketSize::ExtendedPacket.size() == size {
|
||||
Ok(PacketSize::ExtendedPacket)
|
||||
} else if PacketSize::PreSURBChanges.size() == size {
|
||||
Ok(PacketSize::PreSURBChanges)
|
||||
} else {
|
||||
Err(InvalidPacketSize)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,116 +0,0 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::chunking::set::split_into_sets;
|
||||
use crate::packets::PacketSize;
|
||||
|
||||
pub mod fragment;
|
||||
pub mod reconstruction;
|
||||
pub mod set;
|
||||
|
||||
/// The idea behind the process of chunking is to incur as little data overhead as possible due
|
||||
/// to very computationally costly sphinx encapsulation procedure.
|
||||
///
|
||||
/// To achieve this, the underlying message is split into so-called "sets", which are further
|
||||
/// subdivided into the base unit of "fragment" that is directly encapsulated by a Sphinx packet.
|
||||
/// This allows to encapsulate messages of arbitrary length.
|
||||
///
|
||||
/// Each message, regardless of its size, consists of at least a single `Set` that has at least
|
||||
/// a single `Fragment`.
|
||||
///
|
||||
/// Each `Fragment` can have variable, yet fully deterministic, length,
|
||||
/// that depends on its position in the set as well as total number of sets. This is further
|
||||
/// explained in `fragment.rs` file.
|
||||
///
|
||||
/// Similarly, each `Set` can have a variable number of `Fragment`s inside. However, that
|
||||
/// value is more restrictive: if it's the last set into which the message was split
|
||||
/// (or implicitly the only one), it has no lower bound on the number of `Fragment`s.
|
||||
/// (Apart from the restriction of containing at least a single one). If the set is located
|
||||
/// somewhere in the middle, *it must be* full. Finally, regardless of its position, it must also be
|
||||
/// true that it contains no more than `u8::max_value()`, i.e. 255 `Fragment`s.
|
||||
/// Again, the reasoning for this is further explained in `set.rs` file. However, you might
|
||||
/// also want to look at `fragment.rs` to understand the full context behind that design choice.
|
||||
///
|
||||
/// Both of those concepts as well as their structures, i.e. `Set` and `Fragment`
|
||||
/// are further explained in the respective files.
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ChunkingError {
|
||||
InvalidPayloadLengthError,
|
||||
TooBigMessageToSplit,
|
||||
MalformedHeaderError,
|
||||
NoValidProvidersError,
|
||||
NoValidRoutesAvailableError,
|
||||
InvalidTopologyError,
|
||||
TooShortFragmentData,
|
||||
MalformedFragmentData,
|
||||
UnexpectedFragmentCount,
|
||||
}
|
||||
|
||||
pub struct MessageChunker {
|
||||
packet_size: PacketSize,
|
||||
reply_surbs: bool,
|
||||
surb_acks: bool,
|
||||
}
|
||||
|
||||
impl MessageChunker {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_reply_surbs(mut self, reply_surbs: bool) -> Self {
|
||||
self.reply_surbs = reply_surbs;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_surb_acks(mut self, surb_acks: bool) -> Self {
|
||||
self.surb_acks = surb_acks;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.packet_size = packet_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finalize() -> Vec<Vec<u8>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn attach_surb_acks() {}
|
||||
|
||||
pub fn attach_reply_surbs() {}
|
||||
}
|
||||
|
||||
impl Default for MessageChunker {
|
||||
fn default() -> Self {
|
||||
MessageChunker {
|
||||
packet_size: Default::default(),
|
||||
reply_surbs: false,
|
||||
surb_acks: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes the entire message and splits it into bytes chunks that will fit into sphinx packets
|
||||
/// directly. After receiving they can be combined using `reconstruction::MessageReconstructor`
|
||||
/// to obtain the original message back.
|
||||
pub fn split_and_prepare_payloads(message: &[u8]) -> Vec<Vec<u8>> {
|
||||
let fragmented_messages = split_into_sets(message);
|
||||
fragmented_messages
|
||||
.into_iter()
|
||||
.flat_map(|fragment_set| fragment_set.into_iter())
|
||||
.map(|fragment| fragment.into_bytes())
|
||||
.collect()
|
||||
}
|
||||
@@ -12,31 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod addressing;
|
||||
pub mod chunking;
|
||||
pub mod framing;
|
||||
pub mod packets;
|
||||
pub mod utils;
|
||||
|
||||
// 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
|
||||
// have assumed could be possible. A better approach would be to research some Fuzz testing
|
||||
// library like: https://github.com/rust-fuzz/afl.rs and use that instead for the inputs.
|
||||
|
||||
// perhaps it might be useful down the line for interaction testing between client,mixes,etc?
|
||||
|
||||
// re-exporting types and constants available in sphinx
|
||||
pub use sphinx::{
|
||||
constants::{
|
||||
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH,
|
||||
},
|
||||
header::{delays, delays::Delay, ProcessedHeader, SphinxHeader},
|
||||
payload::Payload,
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
Error, ProcessedPacket, Result, SphinxPacket, PACKET_SIZE,
|
||||
};
|
||||
|
||||
// re-exporting this separately to remember to put special attention to below
|
||||
// modules/types/constants when refactoring sphinx crate itself
|
||||
// TODO: replace with sphinx::PublicKey once merged
|
||||
pub use sphinx::key;
|
||||
// re-export sub-crates
|
||||
pub use nymsphinx_acknowledgements as acknowledgements;
|
||||
pub use nymsphinx_addressing as addressing;
|
||||
pub use nymsphinx_chunking as chunking;
|
||||
pub use nymsphinx_cover as cover;
|
||||
pub use nymsphinx_framing as framing;
|
||||
pub use nymsphinx_params as params;
|
||||
pub use nymsphinx_types::*;
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
||||
use crate::{delays, Destination, DestinationAddressBytes, SphinxPacket};
|
||||
use crate::{Error as SphinxError, Node as SphinxNode};
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::time;
|
||||
|
||||
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SphinxPacketEncapsulationError {
|
||||
NoValidProvidersError,
|
||||
InvalidTopologyError,
|
||||
SphinxError(SphinxError),
|
||||
InvalidFirstMixAddress,
|
||||
}
|
||||
|
||||
impl From<SphinxError> for SphinxPacketEncapsulationError {
|
||||
fn from(err: SphinxError) -> Self {
|
||||
SphinxPacketEncapsulationError::SphinxError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymNodeRoutingAddressError> for SphinxPacketEncapsulationError {
|
||||
fn from(_: NymNodeRoutingAddressError) -> Self {
|
||||
use SphinxPacketEncapsulationError::*;
|
||||
InvalidFirstMixAddress
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loop_cover_message_route(
|
||||
our_address: DestinationAddressBytes,
|
||||
route: Vec<SphinxNode>,
|
||||
average_delay: time::Duration,
|
||||
) -> Result<(SocketAddr, SphinxPacket), SphinxPacketEncapsulationError> {
|
||||
encapsulate_message_route(
|
||||
our_address,
|
||||
LOOP_COVER_MESSAGE_PAYLOAD.to_vec(),
|
||||
route,
|
||||
average_delay,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn encapsulate_message_route(
|
||||
destination: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
route: Vec<SphinxNode>,
|
||||
average_delay: time::Duration,
|
||||
) -> Result<(SocketAddr, SphinxPacket), SphinxPacketEncapsulationError> {
|
||||
// in our design we don't care about SURB_ID
|
||||
let destination = Destination::new(destination, Default::default());
|
||||
|
||||
let delays = delays::generate_from_average_duration(route.len(), average_delay);
|
||||
|
||||
// build the packet
|
||||
let packet = SphinxPacket::new(message, &route[..], &destination, &delays, None)?;
|
||||
|
||||
let first_node_address =
|
||||
NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone())?;
|
||||
|
||||
Ok((first_node_address.into(), packet))
|
||||
}
|
||||
@@ -1,2 +1,33 @@
|
||||
pub mod encapsulation;
|
||||
pub mod poisson;
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use rand::Rng;
|
||||
use rand_distr::{Distribution, Exp};
|
||||
use std::time;
|
||||
|
||||
// TODO: ask @AP why we are actually using Distribution::Exp(1/L) rather than just
|
||||
// Distribution::Poisson(L) directly?
|
||||
|
||||
// TODO: should we put an extra trait bound on this to require `CryptoRng`? Could there be any attacks
|
||||
// because of weak rng used?
|
||||
pub fn sample_poisson_duration<R: Rng + ?Sized>(
|
||||
rng: &mut R,
|
||||
average_duration: time::Duration,
|
||||
) -> time::Duration {
|
||||
// this is our internal code used by our traffic streams
|
||||
// the error is only thrown if average delay is less than 0, which will never happen
|
||||
// so call to unwrap is perfectly safe here
|
||||
let exp = Exp::new(1.0 / average_duration.as_nanos() as f64).unwrap();
|
||||
time::Duration::from_nanos(exp.sample(rng).round() as u64)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "nymsphinx-types"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# 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="cd9f09b85de33c60030ba6d7fae613c42cbe1faf" }
|
||||
#sphinx = { path = "../../../../sphinx"}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// re-exporting types and constants available in sphinx
|
||||
pub use sphinx::{
|
||||
constants::{
|
||||
self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH,
|
||||
},
|
||||
crypto::{public_key_from_bytes, PublicKey, SecretKey},
|
||||
header::{self, delays, delays::Delay, ProcessedHeader, SphinxHeader, HEADER_SIZE},
|
||||
packet::builder::{self, DEFAULT_PAYLOAD_SIZE},
|
||||
payload::{Payload, PAYLOAD_OVERHEAD_SIZE},
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
surb::{SURBMaterial, SURB},
|
||||
Error, ProcessedPacket, Result, SphinxPacket,
|
||||
};
|
||||
@@ -16,5 +16,6 @@ rand = "0.7.2"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
|
||||
## internal
|
||||
nymsphinx = {path = "../nymsphinx"}
|
||||
nymsphinx-addressing = {path = "../nymsphinx/addressing"}
|
||||
nymsphinx-types = {path = "../nymsphinx/types"}
|
||||
version-checker = {path = "../version-checker" }
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::filter;
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx_types::Node as SphinxNode;
|
||||
use std::convert::TryInto;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Into<SphinxNode> for Node {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let key_bytes = self.get_pub_key_bytes();
|
||||
let key = nymsphinx::key::new(key_bytes);
|
||||
let key = nymsphinx_types::public_key_from_bytes(key_bytes);
|
||||
|
||||
SphinxNode::new(node_address_bytes, key)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use crate::filter::VersionFilterable;
|
||||
use futures::future::BoxFuture;
|
||||
use itertools::Itertools;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx_types::{Node as SphinxNode, NodeAddressBytes};
|
||||
use rand::seq::IteratorRandom;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
@@ -48,7 +48,7 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone {
|
||||
let mut highest_layer = 0;
|
||||
for mix in self.mix_nodes() {
|
||||
// we need to have extra space for provider
|
||||
if mix.layer > nymsphinx::MAX_PATH_LENGTH as u64 {
|
||||
if mix.layer > nymsphinx_types::MAX_PATH_LENGTH as u64 {
|
||||
return Err(NymTopologyError::InvalidMixLayerError);
|
||||
}
|
||||
highest_layer = max(highest_layer, mix.layer);
|
||||
@@ -90,15 +90,31 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone {
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
// Sets up a route to a specific gateway
|
||||
fn gateway_exists(&self, gateway_address: &NodeAddressBytes) -> bool {
|
||||
let b58_address = gateway_address.to_base58_string();
|
||||
self.gateways()
|
||||
.iter()
|
||||
.find(|&gateway| gateway.pub_key == b58_address)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn random_route_to_gateway(
|
||||
&self,
|
||||
gateway_node: SphinxNode,
|
||||
gateway_address: &NodeAddressBytes,
|
||||
) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
let b58_address = gateway_address.to_base58_string();
|
||||
|
||||
let gateway = self
|
||||
.gateways()
|
||||
.iter()
|
||||
.find(|&gateway| gateway.pub_key == b58_address)
|
||||
.ok_or_else(|| NymTopologyError::NonExistentGatewayError)?
|
||||
.clone();
|
||||
|
||||
Ok(self
|
||||
.random_mix_route()?
|
||||
.into_iter()
|
||||
.chain(std::iter::once(gateway_node))
|
||||
.chain(std::iter::once(gateway.into()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -159,4 +175,5 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone {
|
||||
pub enum NymTopologyError {
|
||||
InvalidMixLayerError,
|
||||
MissingLayerError(Vec<u64>),
|
||||
NonExistentGatewayError,
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::filter;
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx_types::Node as SphinxNode;
|
||||
use std::convert::TryInto;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Into<SphinxNode> for Node {
|
||||
fn into(self) -> SphinxNode {
|
||||
let node_address_bytes = NymNodeRoutingAddress::from(self.host).try_into().unwrap();
|
||||
let key_bytes = self.get_pub_key_bytes();
|
||||
let key = nymsphinx::key::new(key_bytes);
|
||||
let key = nymsphinx_types::public_key_from_bytes(key_bytes);
|
||||
|
||||
SphinxNode::new(node_address_bytes, key)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::filter;
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx_types::Node as SphinxNode;
|
||||
use std::convert::TryInto;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
@@ -54,7 +54,7 @@ impl Into<SphinxNode> for Node {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let key_bytes = self.get_pub_key_bytes();
|
||||
let key = nymsphinx::key::new(key_bytes);
|
||||
let key = nymsphinx_types::public_key_from_bytes(key_bytes);
|
||||
|
||||
SphinxNode::new(node_address_bytes, key)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-gateway"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::auth_token::AuthToken;
|
||||
use crate::types::BinaryRequest::ForwardSphinx;
|
||||
use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
||||
use nymsphinx::params::packet_sizes::PacketSize;
|
||||
use nymsphinx::{DestinationAddressBytes, SphinxPacket};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -27,7 +27,7 @@ use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayRequestsError {
|
||||
IncorrectlyEncodedAddress,
|
||||
RequestOfInvalidSize(usize, usize),
|
||||
RequestOfInvalidSize(usize),
|
||||
MalformedSphinxPacket,
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ impl fmt::Display for GatewayRequestsError {
|
||||
use GatewayRequestsError::*;
|
||||
match self {
|
||||
IncorrectlyEncodedAddress => write!(f, "address field was incorrectly encoded"),
|
||||
RequestOfInvalidSize(actual, expected) => write!(
|
||||
RequestOfInvalidSize(actual) => write!(
|
||||
f,
|
||||
"received request had invalid size. (actual: {}, expected: {})",
|
||||
actual, expected
|
||||
"received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {} (EXTENDED))",
|
||||
actual, PacketSize::ACKPacket.size(), PacketSize::RegularPacket.size(), PacketSize::ExtendedPacket.size()
|
||||
),
|
||||
MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"),
|
||||
}
|
||||
@@ -164,18 +164,18 @@ impl BinaryRequest {
|
||||
let address = NymNodeRoutingAddress::try_from_bytes(&raw_req)?;
|
||||
let addr_offset = address.bytes_min_len();
|
||||
|
||||
if raw_req[addr_offset..].len() != nymsphinx::PACKET_SIZE {
|
||||
Err(GatewayRequestsError::RequestOfInvalidSize(
|
||||
raw_req[addr_offset..].len(),
|
||||
nymsphinx::PACKET_SIZE,
|
||||
))
|
||||
let packet_size = raw_req[addr_offset..].len();
|
||||
if let Err(_) = PacketSize::get_type(packet_size) {
|
||||
// TODO: should this allow AckPacket sizes?
|
||||
|
||||
Err(GatewayRequestsError::RequestOfInvalidSize(packet_size))
|
||||
} else {
|
||||
let sphinx_packet = match SphinxPacket::from_bytes(&raw_req[addr_offset..]) {
|
||||
Ok(packet) => packet,
|
||||
Err(_) => return Err(GatewayRequestsError::MalformedSphinxPacket),
|
||||
};
|
||||
|
||||
Ok(ForwardSphinx {
|
||||
Ok(BinaryRequest::ForwardSphinx {
|
||||
address: address.into(),
|
||||
sphinx_packet,
|
||||
})
|
||||
@@ -201,6 +201,7 @@ impl BinaryRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this will be encrypted, etc.
|
||||
pub fn new_forward_request(address: SocketAddr, sphinx_packet: SphinxPacket) -> BinaryRequest {
|
||||
BinaryRequest::ForwardSphinx {
|
||||
address,
|
||||
|
||||
@@ -61,6 +61,7 @@ where
|
||||
while let Some(sphinx_packet) = self.framed_connection.next().await {
|
||||
match sphinx_packet {
|
||||
Ok(sphinx_packet) => {
|
||||
// rather important TODO:
|
||||
// we *really* need a worker pool here, because if we receive too many packets,
|
||||
// we will spawn too many tasks and starve CPU due to context switching.
|
||||
// (because presumably tokio has some concept of context switching in its
|
||||
|
||||
@@ -16,15 +16,17 @@ use crate::node::client_handling::clients_handler::{
|
||||
ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse,
|
||||
};
|
||||
use crate::node::client_handling::websocket::message_receiver::MixMessageSender;
|
||||
use crate::node::mixnet_handling::sender::OutboundMixMessageSender;
|
||||
use crate::node::storage::inboxes::{ClientStorage, StoreData};
|
||||
use crypto::encryption;
|
||||
use futures::channel::oneshot;
|
||||
use futures::lock::Mutex;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
utils::encapsulation::LOOP_COVER_MESSAGE_PAYLOAD, DestinationAddressBytes,
|
||||
Error as SphinxError, ProcessedPacket, SphinxPacket,
|
||||
};
|
||||
use nymsphinx::acknowledgements::surb_ack::{SURBAck, SURBAckRecoveryError};
|
||||
use nymsphinx::cover::LOOP_COVER_MESSAGE_PAYLOAD;
|
||||
use nymsphinx::params::packet_sizes::PacketSize;
|
||||
use nymsphinx::{DestinationAddressBytes, Error as SphinxError, ProcessedPacket, SphinxPacket};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
@@ -34,9 +36,10 @@ use std::sync::Arc;
|
||||
pub enum MixProcessingError {
|
||||
ReceivedForwardHopError,
|
||||
NonMatchingRecipient,
|
||||
InvalidPayload,
|
||||
UnsupportedSphinxPacketSize(usize),
|
||||
SphinxProcessingError(SphinxError),
|
||||
IOError(String),
|
||||
IncorrectlyFormattedSURBAck(SURBAckRecoveryError),
|
||||
IOError(io::Error),
|
||||
}
|
||||
|
||||
impl From<SphinxError> for MixProcessingError {
|
||||
@@ -52,7 +55,15 @@ impl From<io::Error> for MixProcessingError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
use MixProcessingError::*;
|
||||
|
||||
IOError(e.to_string())
|
||||
IOError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SURBAckRecoveryError> for MixProcessingError {
|
||||
fn from(err: SURBAckRecoveryError) -> Self {
|
||||
use MixProcessingError::*;
|
||||
|
||||
IncorrectlyFormattedSURBAck(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +76,7 @@ pub struct PacketProcessor {
|
||||
available_socket_senders_cache: Arc<Mutex<HashMap<DestinationAddressBytes, MixMessageSender>>>,
|
||||
client_store: ClientStorage,
|
||||
clients_handler_sender: ClientsHandlerRequestSender,
|
||||
ack_sender: OutboundMixMessageSender,
|
||||
}
|
||||
|
||||
impl PacketProcessor {
|
||||
@@ -72,12 +84,14 @@ impl PacketProcessor {
|
||||
secret_key: Arc<encryption::PrivateKey>,
|
||||
clients_handler_sender: ClientsHandlerRequestSender,
|
||||
client_store: ClientStorage,
|
||||
ack_sender: OutboundMixMessageSender,
|
||||
) -> Self {
|
||||
PacketProcessor {
|
||||
available_socket_senders_cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
clients_handler_sender,
|
||||
client_store,
|
||||
secret_key,
|
||||
ack_sender,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +99,16 @@ impl PacketProcessor {
|
||||
&self,
|
||||
sender_channel: Option<MixMessageSender>,
|
||||
message: Vec<u8>,
|
||||
) -> bool {
|
||||
) -> Result<(), Vec<u8>> {
|
||||
match sender_channel {
|
||||
None => false,
|
||||
Some(sender_channel) => sender_channel.unbounded_send(vec![message]).is_ok(),
|
||||
None => Err(message),
|
||||
Some(sender_channel) => {
|
||||
sender_channel
|
||||
.unbounded_send(vec![message])
|
||||
// right now it's a "simpler" case here as we're only ever sending 1 message
|
||||
// at the time, but the channel itself could accept arbitrary many messages at once
|
||||
.map_err(|try_send_err| try_send_err.into_inner().pop().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +160,7 @@ impl PacketProcessor {
|
||||
client_address: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
) -> io::Result<()> {
|
||||
trace!(
|
||||
debug!(
|
||||
"Storing received packet for {:?} on the disk...",
|
||||
client_address.to_base58_string()
|
||||
);
|
||||
@@ -148,6 +168,9 @@ impl PacketProcessor {
|
||||
// not cause our sfw-provider to run out of disk space too quickly.
|
||||
// Eventually this is going to get removed and be replaced by a quota system described in:
|
||||
// https://github.com/nymtech/nym/issues/137
|
||||
|
||||
// JS: I think this would never get called anyway, because if loop cover messages are sent
|
||||
// it means client is online and hence all his messages should be pushed directly to him?
|
||||
if message == LOOP_COVER_MESSAGE_PAYLOAD {
|
||||
debug!("Received a loop cover message - not going to store it");
|
||||
return Ok(());
|
||||
@@ -163,15 +186,15 @@ impl PacketProcessor {
|
||||
) -> Result<(DestinationAddressBytes, Vec<u8>), MixProcessingError> {
|
||||
match packet.process(self.secret_key.deref().inner()) {
|
||||
Ok(ProcessedPacket::ProcessedPacketForwardHop(_, _, _)) => {
|
||||
warn!("Received a forward hop message - those are not implemented for providers");
|
||||
warn!("Received a forward hop message - those are not implemented for gateways");
|
||||
Err(MixProcessingError::ReceivedForwardHopError)
|
||||
}
|
||||
Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, _surb_id, payload)) => {
|
||||
// in our current design, we do not care about the 'surb_id' in the header
|
||||
// as it will always be empty anyway
|
||||
let (payload_destination, message) = payload
|
||||
.try_recover_destination_and_plaintext()
|
||||
.ok_or_else(|| MixProcessingError::InvalidPayload)?;
|
||||
let (payload_destination, message) =
|
||||
payload.try_recover_destination_and_plaintext()?;
|
||||
// TODO: @AP, does that check still make sense?
|
||||
if client_address != payload_destination {
|
||||
return Err(MixProcessingError::NonMatchingRecipient);
|
||||
}
|
||||
@@ -184,28 +207,100 @@ impl PacketProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn split_plaintext_into_ack_and_message(
|
||||
&self,
|
||||
mut extracted_plaintext: Vec<u8>,
|
||||
) -> (Vec<u8>, Vec<u8>) {
|
||||
if extracted_plaintext.len() < SURBAck::len() {
|
||||
// TODO:
|
||||
// TODO:
|
||||
// this is mostly for dev purposes to see if we receive something we did not mean to send
|
||||
// but in an actual system, what should we do? abandon the whole packet?
|
||||
// store client's data regardless?
|
||||
// I'm going to leave this question open for until I've implemented reply SURBs
|
||||
// as they will change the communication between client and gateway so this
|
||||
// if statement might no longer make any sense
|
||||
panic!("received packet without an ack");
|
||||
}
|
||||
|
||||
let plaintext = extracted_plaintext.split_off(SURBAck::len());
|
||||
let ack_data = extracted_plaintext;
|
||||
(ack_data, plaintext)
|
||||
}
|
||||
|
||||
pub(crate) async fn process_sphinx_packet(
|
||||
&mut self,
|
||||
sphinx_packet: SphinxPacket,
|
||||
) -> Result<(), MixProcessingError> {
|
||||
// see if what we got now is an ack or normal packet
|
||||
let packet_len = sphinx_packet.len();
|
||||
// TODO: micro-optimisations:
|
||||
// 1. don't even try to unwrap the packet if it's not one of `PacketSize` variants
|
||||
// 2. if client_address doesn't exist at this gateway, don't do any other work here
|
||||
// (as stupid as this sounds, there's currently no easy way of directly checking if the
|
||||
// client exists here)
|
||||
let (client_address, plaintext) = self.unwrap_sphinx_packet(sphinx_packet)?;
|
||||
let (routable_ack, plaintext) = match packet_len {
|
||||
n if n == PacketSize::ACKPacket.size() => {
|
||||
trace!("received an ack packet!");
|
||||
(None, plaintext)
|
||||
}
|
||||
n if n == PacketSize::RegularPacket.size()
|
||||
|| n == PacketSize::ExtendedPacket.size() =>
|
||||
{
|
||||
trace!("received a normal packet!");
|
||||
let (ack_data, plaintext) = self.split_plaintext_into_ack_and_message(plaintext);
|
||||
let (ack_first_hop, ack_packet) = SURBAck::try_recover_first_hop_packet(&ack_data)?;
|
||||
(Some((ack_first_hop, ack_packet)), plaintext)
|
||||
}
|
||||
n => return Err(MixProcessingError::UnsupportedSphinxPacketSize(n)),
|
||||
};
|
||||
|
||||
let client_sender = self
|
||||
.try_to_obtain_client_ws_message_sender(client_address.clone())
|
||||
.await;
|
||||
|
||||
// TODO: think of a way to prevent having to clone the plaintext here, perhaps make channels use references?
|
||||
// this will, again, take slightly more time, so it's an issue for later
|
||||
if !self.try_push_message_to_client(client_sender, plaintext.clone()) {
|
||||
Ok(self
|
||||
.store_processed_packet_payload(client_address, plaintext)
|
||||
.await?)
|
||||
if let Err(unsent_plaintext) = self.try_push_message_to_client(client_sender, plaintext) {
|
||||
// means we failed to push message directly to the client (it might be offline)
|
||||
// but we don't want to store an ack message for him - he won't be able to decode
|
||||
// it anyway.
|
||||
// TODO: after keybase discussion we *might* want to store them after all
|
||||
if routable_ack.is_none() {
|
||||
trace!("Received an ack for offline client - won't try storing it");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(io_err) = self
|
||||
.store_processed_packet_payload(client_address.clone(), unsent_plaintext)
|
||||
.await
|
||||
{
|
||||
return Err(io_err)?;
|
||||
} else {
|
||||
trace!(
|
||||
"Managed to store packet for {:?} on the disk",
|
||||
client_address.to_base58_string()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
trace!(
|
||||
"Managed to push received packet for {:?} to websocket connection!",
|
||||
client_address.to_base58_string()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if we managed to either push message directly to the [online] client or store it at
|
||||
// it's inbox, it means that it must exist at this gateway, hence we can send the
|
||||
// received ack back into the network
|
||||
if let Some((ack_first_hop, ack_packet)) = routable_ack {
|
||||
trace!(
|
||||
"Sending an ack back into the network. The first hop is {:?}",
|
||||
ack_first_hop
|
||||
);
|
||||
self.ack_sender
|
||||
.unbounded_send((ack_first_hop.into(), ack_packet))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,13 +66,18 @@ impl Gateway {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_mix_socket_listener(&self, clients_handler_sender: ClientsHandlerRequestSender) {
|
||||
fn start_mix_socket_listener(
|
||||
&self,
|
||||
clients_handler_sender: ClientsHandlerRequestSender,
|
||||
ack_sender: OutboundMixMessageSender,
|
||||
) {
|
||||
info!("Starting mix socket listener...");
|
||||
|
||||
let packet_processor = mixnet_handling::PacketProcessor::new(
|
||||
Arc::new(self.sphinx_keypair.private_key().clone()),
|
||||
clients_handler_sender,
|
||||
self.client_inbox_storage.clone(),
|
||||
ack_sender,
|
||||
);
|
||||
|
||||
mixnet_handling::Listener::new(self.config.get_mix_listening_address())
|
||||
@@ -180,7 +185,7 @@ impl Gateway {
|
||||
let mix_forwarding_channel = self.start_packet_forwarder();
|
||||
let clients_handler_sender = self.start_clients_handler();
|
||||
|
||||
self.start_mix_socket_listener(clients_handler_sender.clone());
|
||||
self.start_mix_socket_listener(clients_handler_sender.clone(), mix_forwarding_channel.clone());
|
||||
self.start_client_websocket_listener(mix_forwarding_channel, clients_handler_sender);
|
||||
|
||||
self.start_presence_notifier();
|
||||
|
||||
@@ -118,9 +118,10 @@ impl ClientStorage {
|
||||
let full_store_path = full_store_dir.join(Self::generate_random_file_name(
|
||||
inner_data.filename_length as usize,
|
||||
));
|
||||
debug!(
|
||||
trace!(
|
||||
"going to store: {:?} in file: {:?}",
|
||||
store_data.message, full_store_path
|
||||
store_data.message,
|
||||
full_store_path
|
||||
);
|
||||
|
||||
let mut file = File::create(full_store_path).await?;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-mixnode"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-validator"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user