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:
Jędrzej Stuczyński
2020-06-16 11:22:02 +01:00
committed by GitHub
parent db5540677c
commit b57a548350
66 changed files with 5097 additions and 2393 deletions
Generated
+166 -21
View File
@@ -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",
+7
View File
@@ -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",
+2 -1
View File
@@ -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;
+19 -2
View File
@@ -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;
}
}
}
+73 -46
View File
@@ -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() {
@@ -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?")
}
}
@@ -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
})
}
}
@@ -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?")
}
}
@@ -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 })
}
}
+64 -23
View File
@@ -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);
}
}
+73 -44
View File
@@ -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 {
+6 -9
View File
@@ -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));
}
}
+35
View File
@@ -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,
+1
View File
@@ -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 }}
+6 -13
View File
@@ -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
+3 -2
View File
@@ -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)
}
}
}
}
+84 -82
View File
@@ -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();
}
}
}
-1
View File
@@ -285,7 +285,6 @@ impl PathChecker {
&path[..],
&self.our_destination,
&delays,
None,
)
.unwrap();
+7 -5
View File
@@ -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());
}
}
@@ -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))
}
}
}
+10
View File
@@ -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" }
@@ -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
}
}
@@ -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);
+17
View File
@@ -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
+240
View File
@@ -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()
}
}
@@ -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()
)
);
}
+17
View File
@@ -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" }
+116
View File
@@ -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))
}
+14
View File
@@ -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};
+10
View File
@@ -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" }
+15
View File
@@ -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
-116
View File
@@ -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()
}
+8 -26
View File
@@ -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))
}
+33 -2
View File
@@ -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)
}
+11
View File
@@ -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"}
+27
View File
@@ -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,
};
+2 -1
View File
@@ -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" }
+3 -3
View File
@@ -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)
}
+22 -5
View File
@@ -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,
}
+3 -3
View File
@@ -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)
}
+3 -3
View File
@@ -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
View File
@@ -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"
+12 -11
View File
@@ -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(())
}
}
+7 -2
View File
@@ -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();
+3 -2
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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"