diff --git a/Cargo.lock b/Cargo.lock index 46b0448573d..05cb7ad8504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,6 +439,7 @@ dependencies = [ "gateway-client", "gateway-requests", "log 0.4.11", + "nonexhaustive-delayqueue", "nymsphinx", "pemstore", "rand 0.7.3", @@ -668,6 +669,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dashmap" +version = "4.0.0-rc6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308a6703be2d759cb5fb7b80a23547fe73a8d5ebf70d3a4ca7f0ef4c0bfc2265" +dependencies = [ + "once_cell", +] + [[package]] name = "difference" version = "2.0.0" @@ -1021,7 +1031,7 @@ dependencies = [ "nymsphinx", "tokio 0.2.22", "tokio-tungstenite", - "tungstenite 0.11.1", + "tungstenite", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "wasm-timer", @@ -1041,7 +1051,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "tungstenite 0.11.1", + "tungstenite", ] [[package]] @@ -1602,6 +1612,23 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "mixnode-common" +version = "0.1.0" +dependencies = [ + "dashmap", + "futures 0.3.5", + "log 0.4.11", + "nonexhaustive-delayqueue", + "nymsphinx-acknowledgements", + "nymsphinx-addressing", + "nymsphinx-forwarding", + "nymsphinx-framing", + "nymsphinx-params", + "nymsphinx-types", + "tokio 0.2.22", +] + [[package]] name = "mockito" version = "0.23.3" @@ -1655,6 +1682,13 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nonexhaustive-delayqueue" +version = "0.1.0" +dependencies = [ + "tokio 0.2.22", +] + [[package]] name = "num-integer" version = "0.1.43" @@ -1745,6 +1779,7 @@ dependencies = [ "clap", "config", "crypto", + "dashmap", "directory-client", "dirs 2.0.2", "dotenv", @@ -1752,6 +1787,7 @@ dependencies = [ "gateway-requests", "log 0.4.11", "mixnet-client", + "mixnode-common", "nymsphinx", "pemstore", "pretty_env_logger", @@ -1762,7 +1798,7 @@ dependencies = [ "tokio 0.2.22", "tokio-tungstenite", "tokio-util", - "tungstenite 0.10.1", + "tungstenite", ] [[package]] @@ -1781,6 +1817,7 @@ dependencies = [ "futures 0.3.5", "log 0.4.11", "mixnet-client", + "mixnode-common", "nymsphinx", "pemstore", "pretty_env_logger", @@ -1854,11 +1891,13 @@ dependencies = [ "nymsphinx-anonymous-replies", "nymsphinx-chunking", "nymsphinx-cover", + "nymsphinx-forwarding", "nymsphinx-framing", "nymsphinx-params", "nymsphinx-types", "rand 0.7.3", "rand_distr", + "tokio 0.2.22", "topology", ] @@ -1917,12 +1956,22 @@ dependencies = [ "nymsphinx-acknowledgements", "nymsphinx-addressing", "nymsphinx-chunking", + "nymsphinx-forwarding", "nymsphinx-params", "nymsphinx-types", "rand 0.7.3", "topology", ] +[[package]] +name = "nymsphinx-forwarding" +version = "0.1.0" +dependencies = [ + "nymsphinx-addressing", + "nymsphinx-params", + "nymsphinx-types", +] + [[package]] name = "nymsphinx-framing" version = "0.1.0" @@ -2986,7 +3035,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=18aa34e1a39a5f3f14ba493ded9209658ff2cbfa#18aa34e1a39a5f3f14ba493ded9209658ff2cbfa" +source = "git+https://github.com/nymtech/sphinx?rev=283dcc77dec8ee9ed3bed58c2b878e9c18320723#283dcc77dec8ee9ed3bed58c2b878e9c18320723" dependencies = [ "aes-ctr 0.3.0", "arrayref", @@ -2994,7 +3043,7 @@ dependencies = [ "bs58", "byteorder", "chacha", - "curve25519-dalek 2.1.0", + "curve25519-dalek 3.0.0", "hkdf 0.8.0", "hmac 0.7.1", "lioness", @@ -3353,7 +3402,7 @@ dependencies = [ "log 0.4.11", "pin-project", "tokio 0.2.22", - "tungstenite 0.11.1", + "tungstenite", ] [[package]] @@ -3431,25 +3480,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tungstenite" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" -dependencies = [ - "base64 0.11.0", - "byteorder", - "bytes 0.5.6", - "http", - "httparse", - "input_buffer", - "log 0.4.11", - "rand 0.7.3", - "sha-1 0.8.2", - "url 2.1.1", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.11.1" @@ -3775,7 +3805,7 @@ version = "0.1.0" dependencies = [ "futures 0.3.5", "js-sys", - "tungstenite 0.11.1", + "tungstenite", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index ffa921ff237..df7e29cc904 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,15 @@ members = [ "common/client-libs/validator-client", "common/config", "common/crypto", + "common/mixnode-common", + "common/nonexhaustive-delayqueue", "common/nymsphinx", "common/nymsphinx/acknowledgements", "common/nymsphinx/addressing", "common/nymsphinx/anonymous-replies", "common/nymsphinx/chunking", "common/nymsphinx/cover", + "common/nymsphinx/forwarding", "common/nymsphinx/framing", "common/nymsphinx/params", "common/nymsphinx/types", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 9860343c9c1..e27d6b8057b 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -22,6 +22,7 @@ crypto = { path = "../../common/crypto" } directory-client = { path = "../../common/client-libs/directory-client" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } +nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" } nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } diff --git a/clients/client-core/src/client/cover_traffic_stream.rs b/clients/client-core/src/client/cover_traffic_stream.rs index 0ba912a54de..106521c1544 100644 --- a/clients/client-core/src/client/cover_traffic_stream.rs +++ b/clients/client-core/src/client/cover_traffic_stream.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::mix_traffic::{MixMessage, MixMessageSender}; +use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::topology_control::TopologyAccessor; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; @@ -50,7 +50,7 @@ where /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them /// out to the network without any further delays. - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, /// Represents full address of this client. our_full_destination: Recipient, @@ -101,7 +101,7 @@ impl LoopCoverTrafficStream { average_ack_delay: time::Duration, average_packet_delay: time::Duration, average_cover_message_sending_delay: time::Duration, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, our_full_destination: Recipient, topology_access: TopologyAccessor, ) -> Self { @@ -152,9 +152,7 @@ impl LoopCoverTrafficStream { // - we run out of memory // - the receiver channel is closed // in either case there's no recovery and we can only panic - self.mix_tx - .unbounded_send(MixMessage::new(cover_message.0, cover_message.1)) - .unwrap(); + self.mix_tx.unbounded_send(vec![cover_message]).unwrap(); // TODO: I'm not entirely sure whether this is really required, because I'm not 100% // sure how `yield_now()` works - whether it just notifies the scheduler or whether it diff --git a/clients/client-core/src/client/mix_traffic.rs b/clients/client-core/src/client/mix_traffic.rs index 1817d0dba18..1b092f95b64 100644 --- a/clients/client-core/src/client/mix_traffic.rs +++ b/clients/client-core/src/client/mix_traffic.rs @@ -16,19 +16,12 @@ use futures::channel::mpsc; use futures::StreamExt; use gateway_client::GatewayClient; use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nymsphinx::forwarding::packet::MixPacket; use tokio::runtime::Handle; use tokio::task::JoinHandle; -pub struct MixMessage(NymNodeRoutingAddress, SphinxPacket); -pub type MixMessageSender = mpsc::UnboundedSender; -pub type MixMessageReceiver = mpsc::UnboundedReceiver; - -impl MixMessage { - pub fn new(address: NymNodeRoutingAddress, packet: SphinxPacket) -> Self { - MixMessage(address, packet) - } -} +pub type BatchMixMessageSender = mpsc::UnboundedSender>; +pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver>; const MAX_FAILURE_COUNT: usize = 100; @@ -36,7 +29,7 @@ pub struct MixTrafficController { // TODO: most likely to be replaced by some higher level construct as // later on gateway_client will need to be accessible by other entities gateway_client: GatewayClient, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, // TODO: this is temporary work-around. // in long run `gateway_client` will be moved away from `MixTrafficController` anyway. @@ -44,7 +37,10 @@ pub struct MixTrafficController { } impl MixTrafficController { - pub fn new(mix_rx: MixMessageReceiver, gateway_client: GatewayClient) -> MixTrafficController { + pub fn new( + mix_rx: BatchMixMessageReceiver, + gateway_client: GatewayClient, + ) -> MixTrafficController { MixTrafficController { gateway_client, mix_rx, @@ -52,31 +48,38 @@ impl MixTrafficController { } } - async fn on_message(&mut self, mix_message: MixMessage) { - debug!("Got a mix_message for {:?}", mix_message.0); - match self - .gateway_client - .send_sphinx_packet(mix_message.0, mix_message.1) - .await - { + async fn on_messages(&mut self, mut mix_packets: Vec) { + debug_assert!(!mix_packets.is_empty()); + + let success = if mix_packets.len() == 1 { + let mix_packet = mix_packets.pop().unwrap(); + self.gateway_client.send_mix_packet(mix_packet).await + } else { + self.gateway_client + .batch_send_mix_packets(mix_packets) + .await + }; + + match success { Err(e) => { - error!("Failed to send sphinx packet to the gateway! - {:?}", e); + error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e); self.consecutive_gateway_failure_count += 1; if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT { - // todo: in the future this should initiate a 'graceful' shutdown + // todo: in the future this should initiate a 'graceful' shutdown or try + // to reconnect? panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT) } } Ok(_) => { - trace!("We *might* have managed to forward sphinx packet to the gateway!"); + trace!("We *might* have managed to forward sphinx packet(s) to the gateway!"); self.consecutive_gateway_failure_count = 0; } } } pub async fn run(&mut self) { - while let Some(mix_message) = self.mix_rx.next().await { - self.on_message(mix_message).await; + while let Some(mix_packets) = self.mix_rx.next().await { + self.on_messages(mix_packets).await; } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index 5d44e00695f..34e1edf5bcf 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -13,10 +13,10 @@ // limitations under the License. use super::PendingAcknowledgement; -use crate::client::real_messages_control::acknowledgement_control::ack_delay_queue::AckDelayQueue; use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender; use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; use log::*; +use nonexhaustive_delayqueue::NonExhaustiveDelayQueue; use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::Delay as SphinxDelay; use std::collections::HashMap; @@ -105,7 +105,7 @@ pub(super) struct ActionController { // previous version. /// DelayQueue with all `PendingAcknowledgement` that are waiting to be either received or /// retransmitted if their timer fires up. - pending_acks_timers: AckDelayQueue, + pending_acks_timers: NonExhaustiveDelayQueue, /// Channel for receiving `Action`s from other modules. incoming_actions: UnboundedReceiver, @@ -124,7 +124,7 @@ impl ActionController { ActionController { config, pending_acks_data: HashMap::new(), - pending_acks_timers: AckDelayQueue::new(), + pending_acks_timers: NonExhaustiveDelayQueue::new(), incoming_actions: receiver, retransmission_sender, }, diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 84976629091..2bf96fade2a 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -17,7 +17,7 @@ use super::PendingAcknowledgement; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{ inbound_messages::{InputMessage, InputMessageReceiver}, - real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender}, + real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, topology_control::TopologyAccessor, }; use futures::StreamExt; @@ -40,7 +40,7 @@ where input_receiver: InputMessageReceiver, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, } @@ -55,7 +55,7 @@ where input_receiver: InputMessageReceiver, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, ) -> Self { @@ -85,12 +85,13 @@ where match self .message_preparer .prepare_reply_for_use(data, reply_surb, topology, &self.ack_key) + .await { - Ok((reply_id, sphinx_packet, first_hop)) => { + Ok((mix_packet, reply_id)) => { // TODO: later probably write pending ack here // and deal with them.... // ... somehow - Some(RealMessage::new(first_hop, sphinx_packet, reply_id)) + Some(RealMessage::new(mix_packet, reply_id)) } Err(err) => { // TODO: should we have some mechanism to indicate to the user that the `reply_surb` @@ -140,11 +141,11 @@ where let prepared_fragment = self .message_preparer .prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient) + .await .unwrap(); real_messages.push(RealMessage::new( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, + prepared_fragment.mix_packet, message_chunk.fragment_identifier(), )); @@ -183,11 +184,9 @@ where }; // tells real message sender (with the poisson timer) to send this to the mix network - for real_message in real_messages { - self.real_message_sender - .unbounded_send(real_message) - .unwrap(); - } + self.real_message_sender + .unbounded_send(real_messages) + .unwrap(); } pub(super) async fn run(&mut self) { diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 17a579e5816..7695efca63b 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -18,12 +18,13 @@ use self::{ retransmission_request_listener::RetransmissionRequestListener, sent_notification_listener::SentNotificationListener, }; -use super::real_traffic_stream::RealMessageSender; +use super::real_traffic_stream::BatchRealMessageSender; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor}; use futures::channel::mpsc; use gateway_client::AcknowledgementReceiver; use log::*; +use nymsphinx::params::PacketMode; use nymsphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, @@ -38,7 +39,6 @@ use std::{ }; use tokio::task::JoinHandle; -mod ack_delay_queue; mod acknowledgement_listener; mod action_controller; mod input_message_listener; @@ -87,7 +87,7 @@ impl PendingAcknowledgement { pub(super) struct AcknowledgementControllerConnectors { /// Channel used for forwarding prepared sphinx messages into the poisson sender /// to be sent to the mix network. - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, /// Channel used for receiving raw messages from a client. The messages need to be put /// into sphinx packets first. @@ -104,7 +104,7 @@ pub(super) struct AcknowledgementControllerConnectors { impl AcknowledgementControllerConnectors { pub(super) fn new( - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, input_receiver: InputMessageReceiver, sent_notifier: SentPacketNotificationReceiver, ack_receiver: AcknowledgementReceiver, @@ -131,6 +131,14 @@ pub(super) struct Config { /// Average delay a data packet is going to get delayed at a single mixnode. average_packet_delay: Duration, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + packet_mode: PacketMode, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Config { @@ -139,12 +147,16 @@ impl Config { ack_wait_multiplier: f64, average_ack_delay: Duration, average_packet_delay: Duration, + packet_mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { Config { ack_wait_addition, ack_wait_multiplier, average_ack_delay, average_packet_delay, + packet_mode, + vpn_key_reuse_limit, } } } @@ -185,6 +197,8 @@ where ack_recipient.clone(), config.average_packet_delay, config.average_ack_delay, + config.packet_mode, + config.vpn_key_reuse_limit, ); // will listen for any acks coming from the network diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index 92641e7e432..1e4c01dbc1d 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -16,7 +16,7 @@ use super::action_controller::{Action, ActionSender}; use super::PendingAcknowledgement; use super::RetransmissionRequestReceiver; use crate::client::{ - real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender}, + real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, topology_control::TopologyAccessor, }; use futures::StreamExt; @@ -35,7 +35,7 @@ where ack_recipient: Recipient, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, request_receiver: RetransmissionRequestReceiver, topology_access: TopologyAccessor, } @@ -49,7 +49,7 @@ where ack_recipient: Recipient, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, request_receiver: RetransmissionRequestReceiver, topology_access: TopologyAccessor, ) -> Self { @@ -94,6 +94,7 @@ where let prepared_fragment = self .message_preparer .prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient) + .await .unwrap(); // if we have the ONLY strong reference to the ack data, it means it was removed from the @@ -123,11 +124,10 @@ where // send to `OutQueueControl` to eventually send to the mix network self.real_message_sender - .unbounded_send(RealMessage::new( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, + .unbounded_send(vec![RealMessage::new( + prepared_fragment.mix_packet, frag_id, - )) + )]) .unwrap(); } diff --git a/clients/client-core/src/client/real_messages_control/mod.rs b/clients/client-core/src/client/real_messages_control/mod.rs index a632ae8d1d6..3ed383eef7c 100644 --- a/clients/client-core/src/client/real_messages_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/mod.rs @@ -22,7 +22,7 @@ use self::{ use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{ - inbound_messages::InputMessageReceiver, mix_traffic::MixMessageSender, + inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender, topology_control::TopologyAccessor, }; use futures::channel::mpsc; @@ -30,6 +30,7 @@ use gateway_client::AcknowledgementReceiver; use log::*; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; +use nymsphinx::params::PacketMode; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::sync::Arc; use std::time::Duration; @@ -41,13 +42,34 @@ mod real_traffic_stream; // TODO: ack_key and self_recipient shouldn't really be part of this config pub struct Config { + /// Key used to decrypt contents of received SURBAcks ack_key: Arc, - ack_wait_multiplier: f64, + + /// Given ack timeout in the form a * BASE_DELAY + b, it specifies the additive part `b` ack_wait_addition: Duration, + + /// Given ack timeout in the form a * BASE_DELAY + b, it specifies the multiplier `a` + ack_wait_multiplier: f64, + + /// Address of `this` client. self_recipient: Recipient, + + /// Average delay between sending subsequent packets from this client. + average_message_sending_delay: Duration, + + /// Average delay a data packet is going to get delayed at a single mixnode. average_packet_delay_duration: Duration, + + /// Average delay an acknowledgement packet is going to get delayed at a single mixnode. average_ack_delay_duration: Duration, - average_message_sending_delay: Duration, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + packet_mode: PacketMode, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Config { @@ -59,6 +81,8 @@ impl Config { average_message_sending_delay: Duration, average_packet_delay_duration: Duration, self_recipient: Recipient, + packet_mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { Config { ack_key, @@ -68,6 +92,8 @@ impl Config { average_message_sending_delay, ack_wait_multiplier, ack_wait_addition, + packet_mode, + vpn_key_reuse_limit, } } } @@ -87,7 +113,7 @@ impl RealMessagesController { config: Config, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, ) -> Self { @@ -108,6 +134,8 @@ impl RealMessagesController { config.ack_wait_multiplier, config.average_ack_delay_duration, config.average_packet_delay_duration, + config.packet_mode, + config.vpn_key_reuse_limit, ); let ack_control = AcknowledgementController::new( @@ -143,7 +171,7 @@ impl RealMessagesController { } } - pub(super) async fn run(&mut self) { + pub(super) async fn run(&mut self, vpn_mode: bool) { let mut out_queue_control = self.out_queue_control.take().unwrap(); let mut ack_control = self.ack_control.take().unwrap(); @@ -151,7 +179,7 @@ impl RealMessagesController { // the task to ever finish. This will of course change once we introduce // graceful shutdowns. let out_queue_control_fut = tokio::spawn(async move { - out_queue_control.run_out_queue_control().await; + out_queue_control.run_out_queue_control(vpn_mode).await; error!("The out queue controller has finished execution!"); out_queue_control }); @@ -170,9 +198,9 @@ impl RealMessagesController { // &Handle is only passed for consistency sake with other client modules, but I think // when we get to refactoring, we should apply gateway approach and make it implicit - pub fn start(mut self, handle: &Handle) -> JoinHandle { + pub fn start(mut self, handle: &Handle, vpn_mode: bool) -> JoinHandle { handle.spawn(async move { - self.run().await; + self.run(vpn_mode).await; self }) } diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 0626070575e..f45400c720b 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::mix_traffic::{MixMessage, MixMessageSender}; +use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender; use crate::client::topology_control::TopologyAccessor; use futures::channel::mpsc; @@ -20,12 +20,13 @@ use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::*; use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::{clients::Recipient, nodes::NymNodeRoutingAddress}; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::cover::generate_loop_cover_packet; +use nymsphinx::forwarding::packet::MixPacket; use nymsphinx::utils::sample_poisson_duration; -use nymsphinx::SphinxPacket; use rand::{CryptoRng, Rng}; +use std::collections::VecDeque; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; @@ -76,11 +77,11 @@ where /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them /// out to the network without any further delays. - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, /// Channel used for receiving real, prepared, messages that must be first sufficiently delayed /// before being sent out into the network. - real_receiver: RealMessageReceiver, + real_receiver: BatchRealMessageReceiver, /// Represents full address of this client. our_full_destination: Recipient, @@ -90,23 +91,20 @@ where /// Accessor to the common instance of network topology. topology_access: TopologyAccessor, + + /// Buffer containing all real messages received. It is first exhausted before more are pulled. + received_buffer: VecDeque, } pub(crate) struct RealMessage { - first_hop_address: NymNodeRoutingAddress, - packet: SphinxPacket, + mix_packet: MixPacket, fragment_id: FragmentIdentifier, } impl RealMessage { - pub(crate) fn new( - first_hop_address: NymNodeRoutingAddress, - packet: SphinxPacket, - fragment_id: FragmentIdentifier, - ) -> Self { + pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self { RealMessage { - first_hop_address, - packet, + mix_packet, fragment_id, } } @@ -114,8 +112,8 @@ impl RealMessage { // messages are already prepared, etc. the real point of it is to forward it to mix_traffic // after sufficient delay -pub(crate) type RealMessageSender = mpsc::UnboundedSender; -type RealMessageReceiver = mpsc::UnboundedReceiver; +pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender>; +type BatchRealMessageReceiver = mpsc::UnboundedReceiver>; pub(crate) enum StreamMessage { Cover, @@ -145,14 +143,25 @@ where let next = now + next_poisson_delay; self.next_delay.reset(next); + // check if we have anything immediately available + if let Some(real_available) = self.received_buffer.pop_front() { + return Poll::Ready(Some(StreamMessage::Real(real_available))); + } + // decide what kind of message to send match Pin::new(&mut self.real_receiver).poll_next(cx) { // in the case our real message channel stream was closed, we should also indicate we are closed // (and whoever is using the stream should panic) Poll::Ready(None) => Poll::Ready(None), - // if there's an actual message - return it - Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))), + // if there are more messages available, return first one and store the rest + Poll::Ready(Some(real_messages)) => { + self.received_buffer = real_messages.into(); + // we MUST HAVE received at least ONE message + Poll::Ready(Some(StreamMessage::Real( + self.received_buffer.pop_front().unwrap(), + ))) + } // otherwise construct a dummy one Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)), @@ -168,8 +177,8 @@ where config: Config, ack_key: Arc, sent_notifier: SentPacketNotificationSender, - mix_tx: MixMessageSender, - real_receiver: RealMessageReceiver, + mix_tx: BatchMixMessageSender, + real_receiver: BatchRealMessageReceiver, rng: R, our_full_destination: Recipient, topology_access: TopologyAccessor, @@ -184,9 +193,18 @@ where our_full_destination, rng, topology_access, + received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly } } + fn sent_notify(&self, frag_id: FragmentIdentifier) { + // well technically the message was not sent just yet, but now it's up to internal + // queues and client load rather than the required delay. So realistically we can treat + // whatever is about to happen as negligible additional delay. + trace!("{} is about to get sent to the mixnet", frag_id); + self.sent_notifier.unbounded_send(frag_id).unwrap(); + } + async fn on_message(&mut self, next_message: StreamMessage) { trace!("created new message"); @@ -209,7 +227,7 @@ where } let topology_ref = topology_ref_option.unwrap(); - let cover_message = generate_loop_cover_packet( + generate_loop_cover_packet( &mut self.rng, topology_ref, &*self.ack_key, @@ -217,22 +235,11 @@ where self.config.average_ack_delay, self.config.average_packet_delay, ) - .expect("Somehow failed to generate a loop cover message with a valid topology"); - - MixMessage::new(cover_message.0, cover_message.1) + .expect("Somehow failed to generate a loop cover message with a valid topology") } StreamMessage::Real(real_message) => { - // well technically the message was not sent just yet, but now it's up to internal - // queues and client load rather than the required delay. So realistically we can treat - // whatever is about to happen as negligible additional delay. - trace!( - "{} is about to get sent to the mixnet", - real_message.fragment_id - ); - self.sent_notifier - .unbounded_send(real_message.fragment_id) - .unwrap(); - MixMessage::new(real_message.first_hop_address, real_message.packet) + self.sent_notify(real_message.fragment_id); + real_message.mix_packet } }; @@ -240,7 +247,7 @@ where // - we run out of memory // - the receiver channel is closed // in either case there's no recovery and we can only panic - self.mix_tx.unbounded_send(next_message).unwrap(); + self.mix_tx.unbounded_send(vec![next_message]).unwrap(); // JS: Not entirely sure why or how it fixes stuff, but without the yield call, // the UnboundedReceiver [of mix_rx] will not get a chance to read anything @@ -250,16 +257,42 @@ where tokio::task::yield_now().await; } - pub(crate) async fn run_out_queue_control(&mut self) { + async fn on_batch_received(&mut self, real_messages: Vec) { + let mut mix_packets = Vec::with_capacity(real_messages.len()); + for real_message in real_messages.into_iter() { + self.sent_notify(real_message.fragment_id); + mix_packets.push(real_message.mix_packet); + } + self.mix_tx.unbounded_send(mix_packets).unwrap(); + } + + // Send messages at certain rate and if no real traffic is available, send cover message. + async fn run_normal_out_queue(&mut self) { // we should set initial delay only when we actually start the stream self.next_delay = time::delay_for(sample_poisson_duration( &mut self.rng, self.config.average_message_sending_delay, )); - debug!("Starting out queue controller..."); while let Some(next_message) = self.next().await { self.on_message(next_message).await; } } + + // Send real message as soon as it's available and don't inject ANY cover traffic. + async fn run_vpn_out_queue(&mut self) { + while let Some(next_messages) = self.real_receiver.next().await { + self.on_batch_received(next_messages).await + } + } + + pub(crate) async fn run_out_queue_control(&mut self, vpn_mode: bool) { + if vpn_mode { + debug!("Starting out queue controller in vpn mode..."); + self.run_vpn_out_queue().await + } else { + debug!("Starting out queue controller..."); + self.run_normal_out_queue().await + } + } } diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index b14cdc26ae6..1189f776709 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::path::PathBuf; use std::time; +use std::time::Duration; pub mod persistence; @@ -27,13 +28,16 @@ const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; // all delays are in milliseconds -const DEFAULT_ACK_WAIT_ADDITION: u64 = 800; +const DEFAULT_ACK_WAIT_ADDITION: u64 = 1_500; const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 100; const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 100; const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000; const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; +const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; + +const ZERO_DELAY: Duration = Duration::from_nanos(0); #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -128,6 +132,14 @@ impl Config { self.debug.message_sending_average_delay = 5; // 200 "real" messages / s } + pub fn set_vpn_mode(&mut self, vpn_mode: bool) { + self.client.vpn_mode = vpn_mode; + } + + pub fn set_vpn_key_reuse_limit(&mut self, reuse_limit: usize) { + self.debug.vpn_key_reuse_limit = Some(reuse_limit) + } + pub fn get_id(&self) -> String { self.client.id.clone() } @@ -178,11 +190,19 @@ impl Config { // Debug getters pub fn get_average_packet_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.average_packet_delay) + if self.client.vpn_mode { + ZERO_DELAY + } else { + 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) + if self.client.vpn_mode { + ZERO_DELAY + } else { + time::Duration::from_millis(self.debug.average_ack_delay) + } } pub fn get_ack_wait_multiplier(&self) -> f64 { @@ -198,7 +218,11 @@ impl Config { } pub fn get_message_sending_average_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.message_sending_average_delay) + if self.client.vpn_mode { + ZERO_DELAY + } else { + time::Duration::from_millis(self.debug.message_sending_average_delay) + } } pub fn get_gateway_response_timeout(&self) -> time::Duration { @@ -212,6 +236,21 @@ impl Config { pub fn get_topology_resolution_timeout(&self) -> time::Duration { time::Duration::from_millis(self.debug.topology_resolution_timeout) } + + pub fn get_vpn_mode(&self) -> bool { + self.client.vpn_mode + } + + pub fn get_vpn_key_reuse_limit(&self) -> Option { + match self.get_vpn_mode() { + false => None, + true => Some( + self.debug + .vpn_key_reuse_limit + .unwrap_or_else(|| DEFAULT_VPN_KEY_REUSE_LIMIT), + ), + } + } } impl Default for Config { @@ -233,6 +272,11 @@ pub struct Client { /// URL to the directory server. directory_server: String, + /// Special mode of the system such that all messages are sent as soon as they are received + /// and no cover traffic is generated. If set all message delays are set to 0 and overwriting + /// 'Debug' values will have no effect. + vpn_mode: bool, + /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -278,6 +322,7 @@ impl Default for Client { Client { id: "".to_string(), directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), + vpn_mode: false, private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_encryption_key_file: Default::default(), @@ -388,6 +433,10 @@ pub struct Debug { /// did not reach its destination. /// The provided value is interpreted as milliseconds. topology_resolution_timeout: u64, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Default for Debug { @@ -402,6 +451,7 @@ impl Default for Debug { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, + vpn_key_reuse_limit: None, } } } diff --git a/clients/client-core/src/config/template.rs b/clients/client-core/src/config/template.rs deleted file mode 100644 index fbe0f404c24..00000000000 --- a/clients/client-core/src/config/template.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. - r#" -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base client config options ##### - -[client] -# Human readable ID of this particular client. -id = '{{ client.id }}' - -# URL to the directory server. -directory_server = '{{ client.directory_server }}' - -# Path to file containing private identity key. -private_identity_key_file = '{{ client.private_identity_key_file }}' - -# Path to file containing public identity key. -public_identity_key_file = '{{ client.public_identity_key_file }}' - -# Path to file containing private encryption key. -private_encryption_key_file = '{{ client.private_encryption_key_file }}' - -# Path to file containing public encryption key. -public_encryption_key_file = '{{ client.public_encryption_key_file }}' - -# Full path to file containing reply encryption keys of all reply-SURBs we have ever -# sent but not received back. -reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' - -##### additional client config options ##### - -# ID of the gateway from which the client should be fetching messages. -gateway_id = '{{ client.gateway_id }}' - -# Address of the gateway listener to which all client requests should be sent. -gateway_listener = '{{ client.gateway_listener }}' - -# A gateway specific, optional, base58 stringified shared key used for -# communication with particular gateway. -gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' - -# Path to file containing key used for encrypting and decrypting the content of an -# acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ client.ack_key_file }}' - -##### advanced configuration options ##### - -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ client.nym_root_directory }}' - - -##### socket config options ##### - -[socket] - -# allowed values are 'WebSocket' or 'None' -socket_type = '{{ socket.socket_type }}' - -# if applicable (for the case of 'WebSocket'), the port on which the client -# will be listening for incoming requests -listening_port = {{ socket.listening_port }} - - -##### logging configuration options ##### - -[logging] - -# TODO - - -##### debug configuration options ##### -# The following options should not be modified unless you know EXACTLY what you are doing -# as if set incorrectly, they may impact your anonymity. - -[debug] - -average_packet_delay = {{ debug.average_packet_delay }} -average_ack_delay = {{ debug.average_ack_delay }} -loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} -message_sending_average_delay = {{ debug.message_sending_average_delay }} - -"# -} diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index fbe0f404c24..b4185891a29 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -30,6 +30,11 @@ id = '{{ client.id }}' # URL to the directory server. directory_server = '{{ client.directory_server }}' +# Special mode of the system such that all messages are sent as soon as they are received +# and no cover traffic is generated. If set all message delays are set to 0 and overwriting +# 'Debug' values will have no effect. +vpn_mode = {{ client.vpn_mode }} + # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 9931f36829a..ee3d35e57be 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -20,7 +20,7 @@ use client_core::client::inbound_messages::{ }; use client_core::client::key_manager::KeyManager; use client_core::client::mix_traffic::{ - MixMessageReceiver, MixMessageSender, MixTrafficController, + BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController, }; use client_core::client::real_messages_control; use client_core::client::real_messages_control::RealMessagesController; @@ -43,6 +43,7 @@ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::anonymous_replies::ReplySURB; +use nymsphinx::params::PacketMode; use nymsphinx::receiver::ReconstructedMessage; use tokio::runtime::Runtime; @@ -100,7 +101,7 @@ impl NymClient { fn start_cover_traffic_stream( &self, topology_accessor: TopologyAccessor, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, ) { info!("Starting loop cover traffic stream..."); // we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())" @@ -128,8 +129,14 @@ impl NymClient { reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, ) { + let packet_mode = if self.config.get_base().get_vpn_mode() { + PacketMode::VPN + } else { + PacketMode::Mix + }; + let controller_config = real_messages_control::Config::new( self.key_manager.ack_key(), self.config.get_base().get_ack_wait_multiplier(), @@ -138,6 +145,8 @@ impl NymClient { self.config.get_base().get_message_sending_average_delay(), self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), + packet_mode, + self.config.get_base().get_vpn_key_reuse_limit(), ); info!("Starting real traffic stream..."); @@ -154,7 +163,8 @@ impl NymClient { reply_key_storage, ) }); - real_messages_controller.start(self.runtime.handle()); + real_messages_controller + .start(self.runtime.handle(), self.config.get_base().get_vpn_mode()); } // buffer controlling all messages fetched from provider @@ -250,7 +260,7 @@ impl NymClient { // requests? fn start_mix_traffic_controller( &mut self, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, gateway_client: GatewayClient, ) { info!("Starting mix traffic controller..."); @@ -330,6 +340,8 @@ impl NymClient { } pub fn start(&mut self) { + let vpn_mode = true; + info!("Starting nym client"); // channels for inter-component communication // TODO: make the channels be internally created by the relevant components @@ -378,7 +390,10 @@ impl NymClient { input_receiver, sphinx_message_sender.clone(), ); - self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + + if vpn_mode { + self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + } match self.config.get_socket_type() { SocketType::WebSocket => { diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 56aeb628a1d..1f99d0f4d25 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -64,6 +64,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Port for the socket (if applicable) to listen on in all subsequent runs") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("fastmode") .long("fastmode") .hidden(true) // this will prevent this flag from being displayed in `--help` diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index ceb9c81aeaa..927ff087367 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -31,6 +31,10 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_socket(SocketType::None); } + if matches.is_present("vpn-mode") { + config.get_base_mut().set_vpn_mode(true); + } + if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { if let Err(err) = port { // if port was overridden, it must be parsable diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index eb0486f918b..de577842057 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -47,6 +47,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .long("disable-socket") .help("Whether to not start the websocket") ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("port") .short("p") .long("port") diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 80861a7bc43..ece793154b3 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -30,6 +30,11 @@ id = '{{ client.id }}' # URL to the directory server. directory_server = '{{ client.directory_server }}' +# Special mode of the system such that all messages are sent as soon as they are received +# and no cover traffic is generated. If set all message delays are set to 0 and overwriting +# 'Debug' values will have no effect. +vpn_mode = {{ client.vpn_mode }} + # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 727752a160b..5f1703b0932 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -23,7 +23,7 @@ use client_core::client::inbound_messages::{ }; use client_core::client::key_manager::KeyManager; use client_core::client::mix_traffic::{ - MixMessageReceiver, MixMessageSender, MixTrafficController, + BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController, }; use client_core::client::real_messages_control::RealMessagesController; use client_core::client::received_buffer::{ @@ -43,6 +43,7 @@ use gateway_client::{ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; +use nymsphinx::params::PacketMode; use tokio::runtime::Runtime; pub(crate) mod config; @@ -88,7 +89,7 @@ impl NymClient { fn start_cover_traffic_stream( &self, topology_accessor: TopologyAccessor, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, ) { info!("Starting loop cover traffic stream..."); // we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())" @@ -116,8 +117,14 @@ impl NymClient { reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, ) { + let packet_mode = if self.config.get_base().get_vpn_mode() { + PacketMode::VPN + } else { + PacketMode::Mix + }; + let controller_config = client_core::client::real_messages_control::Config::new( self.key_manager.ack_key(), self.config.get_base().get_ack_wait_multiplier(), @@ -126,6 +133,8 @@ impl NymClient { self.config.get_base().get_message_sending_average_delay(), self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), + packet_mode, + self.config.get_base().get_vpn_key_reuse_limit(), ); info!("Starting real traffic stream..."); @@ -142,7 +151,8 @@ impl NymClient { reply_key_storage, ) }); - real_messages_controller.start(self.runtime.handle()); + real_messages_controller + .start(self.runtime.handle(), self.config.get_base().get_vpn_mode()); } // buffer controlling all messages fetched from provider @@ -238,7 +248,7 @@ impl NymClient { // requests? fn start_mix_traffic_controller( &mut self, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, gateway_client: GatewayClient, ) { info!("Starting mix traffic controller..."); diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index a22b5bfc85b..1f0c43b9d1a 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -65,6 +65,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Port for the socket to listen on in all subsequent runs") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("fastmode") .long("fastmode") .hidden(true) // this will prevent this flag from being displayed in `--help` diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 2906c40d9ed..dabb25bdf89 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -27,6 +27,10 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config.get_base_mut().with_gateway_id(gateway_id); } + if matches.is_present("vpn-mode") { + config.get_base_mut().set_vpn_mode(true); + } + if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { if let Err(err) = port { // if port was overridden, it must be parsable diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index f8c9aea11c3..04f76a4d768 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -48,6 +48,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("port") .short("p") .long("port") diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index acaf085514f..50507c17582 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -20,6 +20,7 @@ use gateway_client::GatewayClient; use js_sys::Promise; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; +use nymsphinx::params::PacketMode; use nymsphinx::preparer::MessagePreparer; use rand::rngs::OsRng; use received_processor::ReceivedMessagesProcessor; @@ -38,6 +39,8 @@ const DEFAULT_RNG: OsRng = OsRng; const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200); const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200); const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_PACKET_MODE: PacketMode = PacketMode::VPN; +const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; #[wasm_bindgen] pub struct NymClient { @@ -149,6 +152,8 @@ impl NymClient { client.self_recipient(), DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_AVERAGE_ACK_DELAY, + DEFAULT_PACKET_MODE, + Some(DEFAULT_VPN_KEY_REUSE_LIMIT), ); let received_processor = ReceivedMessagesProcessor::new( @@ -186,23 +191,21 @@ impl NymClient { .prepare_and_split_message(message_bytes, false, topology) .expect("failed to split the message"); - let mut socket_messages = Vec::with_capacity(split_message.len()); + let mut mix_packets = Vec::with_capacity(split_message.len()); for message_chunk in split_message { // don't bother with acks etc. for time being let prepared_fragment = message_preparer .prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient) + .await .unwrap(); console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay); - socket_messages.push(( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, - )); + mix_packets.push(prepared_fragment.mix_packet); } self.gateway_client .as_mut() .unwrap() - .batch_send_sphinx_packets(socket_messages) + .batch_send_mix_packets(mix_packets) .await .unwrap(); self diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 36e75d5dc9c..0af2c1efac2 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -25,7 +25,7 @@ use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::authentication::iv::AuthenticationIV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys, DEFAULT_RNG}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nymsphinx::forwarding::packet::MixPacket; use std::convert::TryFrom; use std::sync::Arc; use std::time::Duration; @@ -320,9 +320,9 @@ impl GatewayClient { } } - pub async fn batch_send_sphinx_packets( + pub async fn batch_send_mix_packets( &mut self, - packets: Vec<(NymNodeRoutingAddress, SphinxPacket)>, + packets: Vec, ) -> Result<(), GatewayClientError> { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); @@ -333,8 +333,8 @@ impl GatewayClient { let messages: Vec<_> = packets .into_iter() - .map(|(address, packet)| { - BinaryRequest::new_forward_request(address, packet).into_ws_message( + .map(|mix_packet| { + BinaryRequest::new_forward_request(mix_packet).into_ws_message( self.shared_key .as_ref() .expect("no shared key present even though we're authenticated!"), @@ -347,10 +347,9 @@ impl GatewayClient { } // TODO: possibly make responses optional - pub async fn send_sphinx_packet( + pub async fn send_mix_packet( &mut self, - address: NymNodeRoutingAddress, - packet: SphinxPacket, + mix_packet: MixPacket, ) -> Result<(), GatewayClientError> { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); @@ -360,7 +359,7 @@ impl GatewayClient { } // note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should // be more explicit in the naming? - let msg = BinaryRequest::new_forward_request(address, packet).into_ws_message( + let msg = BinaryRequest::new_forward_request(mix_packet).into_ws_message( self.shared_key .as_ref() .expect("no shared key present even though we're authenticated!"), diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs new file mode 100644 index 00000000000..2a69ae7bb9d --- /dev/null +++ b/common/client-libs/mixnet-client/src/client.rs @@ -0,0 +1,129 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::connection_manager::{ConnectionManager, ConnectionManagerSender}; +use futures::channel::oneshot; +use futures::future::AbortHandle; +use log::*; +use nymsphinx::framing::packet::FramedSphinxPacket; +use nymsphinx::params::PacketMode; +use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use std::collections::HashMap; +use std::io; +use std::net::SocketAddr; +use std::time::Duration; + +pub struct Config { + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, +} + +impl Config { + pub fn new( + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + ) -> Self { + Config { + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + } + } +} + +pub struct Client { + connections_managers: HashMap, + maximum_reconnection_backoff: Duration, + initial_reconnection_backoff: Duration, + initial_connection_timeout: Duration, +} + +impl Client { + pub fn new(config: Config) -> Client { + Client { + connections_managers: HashMap::new(), + initial_reconnection_backoff: config.initial_reconnection_backoff, + maximum_reconnection_backoff: config.maximum_reconnection_backoff, + initial_connection_timeout: config.initial_connection_timeout, + } + } + + async fn start_new_connection_manager( + &mut self, + address: SocketAddr, + ) -> (ConnectionManagerSender, AbortHandle) { + let (sender, abort_handle) = ConnectionManager::new( + address, + self.initial_reconnection_backoff, + self.maximum_reconnection_backoff, + self.initial_connection_timeout, + ) + .await + .spawn_abortable(); + + (sender, abort_handle) + } + + // if wait_for_response is set to true, we will get information about any possible IO errors + // as well as (once implemented) received replies, however, this will also cause way longer + // waiting periods + pub async fn send( + &mut self, + address: NymNodeRoutingAddress, + packet: SphinxPacket, + packet_mode: PacketMode, + wait_for_response: bool, + ) -> io::Result<()> { + trace!("Sending packet to {:?}", address); + let socket_address = address.into(); + + if !self.connections_managers.contains_key(&socket_address) { + debug!( + "There is no existing connection to {:?} - it will be established now", + address + ); + + let (new_manager_sender, abort_handle) = + self.start_new_connection_manager(socket_address).await; + self.connections_managers + .insert(socket_address, (new_manager_sender, abort_handle)); + } + + let manager = self.connections_managers.get_mut(&socket_address).unwrap(); + + let framed_packet = FramedSphinxPacket::new(packet, packet_mode); + + if wait_for_response { + let (res_tx, res_rx) = oneshot::channel(); + manager + .0 + .unbounded_send((framed_packet, Some(res_tx))) + .unwrap(); + res_rx.await.unwrap() + } else { + manager.0.unbounded_send((framed_packet, None)).unwrap(); + Ok(()) + } + } +} + +impl Drop for Client { + fn drop(&mut self) { + for (_, abort_handle) in self.connections_managers.values() { + abort_handle.abort() + } + } +} diff --git a/common/client-libs/mixnet-client/src/connection_manager/mod.rs b/common/client-libs/mixnet-client/src/connection_manager/mod.rs index 107e01de809..56fb76ba7be 100644 --- a/common/client-libs/mixnet-client/src/connection_manager/mod.rs +++ b/common/client-libs/mixnet-client/src/connection_manager/mod.rs @@ -19,19 +19,19 @@ use futures::future::{abortable, AbortHandle}; use futures::task::Poll; use futures::{SinkExt, StreamExt}; use log::*; -use nymsphinx::SphinxPacket; +use nymsphinx::framing::packet::FramedSphinxPacket; use std::io; use std::net::SocketAddr; use std::time::Duration; -use tokio::runtime::Handle; mod reconnector; mod writer; pub(crate) type ResponseSender = Option>>; -pub(crate) type ConnectionManagerSender = mpsc::UnboundedSender<(SphinxPacket, ResponseSender)>; -type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(SphinxPacket, ResponseSender)>; +pub(crate) type ConnectionManagerSender = + mpsc::UnboundedSender<(FramedSphinxPacket, ResponseSender)>; +type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(FramedSphinxPacket, ResponseSender)>; enum ConnectionState<'a> { Writing(ConnectionWriter), @@ -96,8 +96,8 @@ impl<'a> ConnectionManager<'static> { async fn run(mut self) { while let Some(msg) = self.conn_rx.next().await { - let (msg_content, res_ch) = msg; - let res = self.handle_new_packet(msg_content).await; + let (framed_packet, res_ch) = msg; + let res = self.handle_new_packet(framed_packet).await; if let Some(res_ch) = res_ch { if let Err(e) = res_ch.send(res) { error!( @@ -110,11 +110,11 @@ impl<'a> ConnectionManager<'static> { } /// consumes Self and returns channel for communication as well as an `AbortHandle` - pub(crate) fn start_abortable(self, handle: &Handle) -> (ConnectionManagerSender, AbortHandle) { + pub(crate) fn spawn_abortable(self) -> (ConnectionManagerSender, AbortHandle) { let sender_clone = self.conn_tx.clone(); let (abort_fut, abort_handle) = abortable(self.run()); - handle.spawn(async move { abort_fut.await }); + tokio::spawn(async move { abort_fut.await }); (sender_clone, abort_handle) } @@ -122,7 +122,7 @@ impl<'a> ConnectionManager<'static> { // Possible future TODO: `Framed<...>` is both a Sink and a Stream, // so it is possible to read any responses we might receive (it is also duplex, so that could be // done while writing packets themselves). But it'd require slight additions to `SphinxCodec` - async fn handle_new_packet(&mut self, packet: SphinxPacket) -> io::Result<()> { + async fn handle_new_packet(&mut self, packet: FramedSphinxPacket) -> io::Result<()> { // we don't do a match here as it's possible to transition from ConnectionState::Reconnecting to ConnectionState::Writing // in this function call. And if that happens, we want to send the packet we have received. if let ConnectionState::Reconnecting(conn_reconnector) = &mut self.state { @@ -145,7 +145,7 @@ impl<'a> ConnectionManager<'static> { // we must be in writing state if we are here, either by being here from beginning or just // transitioning from reconnecting if let ConnectionState::Writing(conn_writer) = &mut self.state { - if let Err(e) = conn_writer.send(packet).await { + return if let Err(e) = conn_writer.send(packet).await { warn!( "Failed to forward message - {:?}. Starting reconnection procedure...", e @@ -155,13 +155,13 @@ impl<'a> ConnectionManager<'static> { self.reconnection_backoff, self.maximum_reconnection_backoff, )); - return Err(io::Error::new( + Err(io::Error::new( io::ErrorKind::BrokenPipe, "connection is broken - reconnection is in progress", - )); + )) } else { - return Ok(()); - } + Ok(()) + }; } unreachable!(); diff --git a/common/client-libs/mixnet-client/src/connection_manager/writer.rs b/common/client-libs/mixnet-client/src/connection_manager/writer.rs index 1208049b017..b23caec8124 100644 --- a/common/client-libs/mixnet-client/src/connection_manager/writer.rs +++ b/common/client-libs/mixnet-client/src/connection_manager/writer.rs @@ -14,8 +14,8 @@ use futures::task::{Context, Poll}; use futures::Sink; -use nymsphinx::framing::{SphinxCodec, SphinxCodecError}; -use nymsphinx::SphinxPacket; +use nymsphinx::framing::codec::{SphinxCodec, SphinxCodecError}; +use nymsphinx::framing::packet::FramedSphinxPacket; use std::pin::Pin; use tokio_util::codec::Framed; @@ -31,14 +31,14 @@ impl ConnectionWriter { } } -impl Sink for ConnectionWriter { +impl Sink for ConnectionWriter { type Error = SphinxCodecError; fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.framed_connection).poll_ready(cx) } - fn start_send(mut self: Pin<&mut Self>, item: SphinxPacket) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, item: FramedSphinxPacket) -> Result<(), Self::Error> { Pin::new(&mut self.framed_connection).start_send(item) } diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs new file mode 100644 index 00000000000..d68d9e5c80f --- /dev/null +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -0,0 +1,70 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::{Client, Config}; +use futures::channel::mpsc; +use futures::StreamExt; +use log::*; +use nymsphinx::forwarding::packet::MixPacket; +use std::time::Duration; + +pub type MixForwardingSender = mpsc::UnboundedSender; +type MixForwardingReceiver = mpsc::UnboundedReceiver; + +/// A specialisation of client such that it forwards any received packets on the channel into the +/// mix network immediately, i.e. will not try to listen for any responses. +pub struct PacketForwarder { + mixnet_client: Client, + packet_receiver: MixForwardingReceiver, +} + +impl PacketForwarder { + pub fn new( + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + ) -> (PacketForwarder, MixForwardingSender) { + let client_config = Config::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + ); + + let (packet_sender, packet_receiver) = mpsc::unbounded(); + + ( + PacketForwarder { + mixnet_client: Client::new(client_config), + packet_receiver, + }, + packet_sender, + ) + } + + pub async fn run(&mut self) { + while let Some(mix_packet) = self.packet_receiver.next().await { + trace!("Going to forward packet to {:?}", mix_packet.next_hop()); + + let next_hop = mix_packet.next_hop(); + let packet_mode = mix_packet.packet_mode(); + let sphinx_packet = mix_packet.into_sphinx_packet(); + // we don't care about responses, we just want to fire packets + // as quickly as possible + self.mixnet_client + .send(next_hop, sphinx_packet, packet_mode, false) + .await + .unwrap(); // if we're not waiting for response, we MUST get an Ok + } + } +} diff --git a/common/client-libs/mixnet-client/src/lib.rs b/common/client-libs/mixnet-client/src/lib.rs index d4c72240b06..cb66dadb90a 100644 --- a/common/client-libs/mixnet-client/src/lib.rs +++ b/common/client-libs/mixnet-client/src/lib.rs @@ -12,272 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::connection_manager::{ConnectionManager, ConnectionManagerSender}; -use futures::channel::oneshot; -use futures::future::AbortHandle; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::collections::HashMap; -use std::io; -use std::net::SocketAddr; -use std::time::Duration; -use tokio::runtime::Handle; - -mod connection_manager; - -pub struct Config { - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, -} - -impl Config { - pub fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> Self { - Config { - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - } - } -} - -pub struct Client { - runtime_handle: Handle, - connections_managers: HashMap, - maximum_reconnection_backoff: Duration, - initial_reconnection_backoff: Duration, - initial_connection_timeout: Duration, -} - -impl Client { - pub fn new(config: Config) -> Client { - Client { - // if the function is not called within tokio runtime context, this will panic - // but perhaps the code should be better structured to completely avoid this call - runtime_handle: Handle::try_current() - .expect("The client MUST BE used within tokio runtime context"), - connections_managers: HashMap::new(), - initial_reconnection_backoff: config.initial_reconnection_backoff, - maximum_reconnection_backoff: config.maximum_reconnection_backoff, - initial_connection_timeout: config.initial_connection_timeout, - } - } - - async fn start_new_connection_manager( - &mut self, - address: SocketAddr, - ) -> (ConnectionManagerSender, AbortHandle) { - let (sender, abort_handle) = ConnectionManager::new( - address, - self.initial_reconnection_backoff, - self.maximum_reconnection_backoff, - self.initial_connection_timeout, - ) - .await - .start_abortable(&self.runtime_handle); - - (sender, abort_handle) - } - - // if wait_for_response is set to true, we will get information about any possible IO errors - // as well as (once implemented) received replies, however, this will also cause way longer - // waiting periods - pub async fn send( - &mut self, - address: NymNodeRoutingAddress, - packet: SphinxPacket, - wait_for_response: bool, - ) -> io::Result<()> { - trace!("Sending packet to {:?}", address); - let socket_address = address.into(); - - if !self.connections_managers.contains_key(&socket_address) { - debug!( - "There is no existing connection to {:?} - it will be established now", - address - ); - - let (new_manager_sender, abort_handle) = - self.start_new_connection_manager(socket_address).await; - self.connections_managers - .insert(socket_address, (new_manager_sender, abort_handle)); - } - - let manager = self.connections_managers.get_mut(&socket_address).unwrap(); - - if wait_for_response { - let (res_tx, res_rx) = oneshot::channel(); - manager.0.unbounded_send((packet, Some(res_tx))).unwrap(); - res_rx.await.unwrap() - } else { - manager.0.unbounded_send((packet, None)).unwrap(); - Ok(()) - } - } -} - -impl Drop for Client { - fn drop(&mut self) { - for (_, abort_handle) in self.connections_managers.values() { - abort_handle.abort() - } - } -} - -/* - The below tests weren't extremely reliable to begin with, however, - to restore them as they were before, we'd need to expose some kind of 'SphinxPacket::test_fixture()` - function. -*/ -// -// #[cfg(test)] -// mod tests { -// use super::*; -// use std::str; -// use std::time; -// use tokio::prelude::*; -// -// const SERVER_MSG_LEN: usize = 16; -// const CLOSE_MESSAGE: [u8; SERVER_MSG_LEN] = [0; SERVER_MSG_LEN]; -// -// struct DummyServer { -// received_buf: Vec>, -// listener: tokio::net::TcpListener, -// } -// -// impl DummyServer { -// async fn new(address: SocketAddr) -> Self { -// DummyServer { -// received_buf: Vec::new(), -// listener: tokio::net::TcpListener::bind(address).await.unwrap(), -// } -// } -// -// fn get_received(&self) -> Vec> { -// self.received_buf.clone() -// } -// -// // this is only used in tests so slightly higher logging levels are fine -// async fn listen_until(mut self, close_message: &[u8]) -> Self { -// let (mut socket, _) = self.listener.accept().await.unwrap(); -// loop { -// let mut buf = [0u8; SERVER_MSG_LEN]; -// match socket.read(&mut buf).await { -// Ok(n) if n == 0 => { -// info!("Remote connection closed"); -// return self; -// } -// Ok(n) => { -// info!("received ({}) - {:?}", n, str::from_utf8(buf[..n].as_ref())); -// -// if buf[..n].as_ref() == close_message { -// info!("closing..."); -// socket.shutdown(std::net::Shutdown::Both).unwrap(); -// return self; -// } else { -// self.received_buf.push(buf[..n].to_vec()); -// } -// } -// Err(e) => { -// panic!("failed to read from socket; err = {:?}", e); -// } -// }; -// } -// } -// } -// -// #[test] -// fn client_reconnects_to_server_after_it_went_down() { -// let mut rt = tokio::runtime::Runtime::new().unwrap(); -// let addr = "127.0.0.1:6000".parse().unwrap(); -// let reconnection_backoff = Duration::from_secs(1); -// let timeout = Duration::from_secs(1); -// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout); -// -// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()]; -// -// let dummy_server = rt.block_on(DummyServer::new(addr)); -// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// let mut c = rt.enter(|| Client::new(client_config)); -// -// for msg in &messages_to_send { -// rt.block_on(c.send(addr, msg.clone(), true)).unwrap(); -// } -// -// // kill server -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// let received_messages = rt -// .block_on(finished_dummy_server_future) -// .unwrap() -// .get_received(); -// -// assert_eq!(received_messages, messages_to_send); -// -// // try to send - go into reconnection -// let post_kill_message = [3u8; SERVER_MSG_LEN].to_vec(); -// -// // we are trying to send to killed server -// assert!(rt -// .block_on(c.send(addr, post_kill_message.clone(), true)) -// .is_err()); -// -// let new_dummy_server = rt.block_on(DummyServer::new(addr)); -// let new_server_future = rt.spawn(new_dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// // keep sending after we leave reconnection backoff and reconnect -// loop { -// if rt -// .block_on(c.send(addr, post_kill_message.clone(), true)) -// .is_ok() -// { -// break; -// } -// rt.block_on( -// async move { tokio::time::delay_for(time::Duration::from_millis(50)).await }, -// ); -// } -// -// // kill the server to ensure it actually got the message -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// let new_received_messages = rt.block_on(new_server_future).unwrap().get_received(); -// assert_eq!(post_kill_message.to_vec(), new_received_messages[0]); -// } -// -// #[test] -// fn server_receives_all_sent_messages_when_up() { -// let mut rt = tokio::runtime::Runtime::new().unwrap(); -// let addr = "127.0.0.1:6001".parse().unwrap(); -// let reconnection_backoff = Duration::from_secs(2); -// let timeout = Duration::from_secs(1); -// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout); -// -// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()]; -// -// let dummy_server = rt.block_on(DummyServer::new(addr)); -// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// let mut c = rt.enter(|| Client::new(client_config)); -// -// for msg in &messages_to_send { -// rt.block_on(c.send(addr, msg.clone(), true)).unwrap(); -// } -// -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// -// // the server future should have already been resolved -// let received_messages = rt -// .block_on(finished_dummy_server_future) -// .unwrap() -// .get_received(); -// -// assert_eq!(received_messages, messages_to_send); -// } -// } +pub mod client; +pub(crate) mod connection_manager; +pub mod forwarder; diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml new file mode 100644 index 00000000000..c1fe96df6ec --- /dev/null +++ b/common/mixnode-common/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mixnode-common" +version = "0.1.0" +authors = ["Jędrzej Stuczyński "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# using 4.0.0 release candidate as it's faster than 3.X and more importantly it resolves edge cases deadlocks +dashmap = "4.0.0-rc6" +futures = "0.3" +log = "0.4" +nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } +nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } +nymsphinx-addressing = { path = "../nymsphinx/addressing" } +nymsphinx-forwarding = { path = "../nymsphinx/forwarding" } +nymsphinx-framing = { path = "../nymsphinx/framing" } +nymsphinx-params = { path = "../nymsphinx/params" } +nymsphinx-types = { path = "../nymsphinx/types" } +tokio = { version = "0.2", features = ["time", "macros", "rt-core"] } \ No newline at end of file diff --git a/common/mixnode-common/src/cached_packet_processor/cache.rs b/common/mixnode-common/src/cached_packet_processor/cache.rs new file mode 100644 index 00000000000..b58d57faadb --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/cache.rs @@ -0,0 +1,176 @@ +// 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 dashmap::{DashMap, ElementGuard}; +use futures::channel::mpsc; +use log::*; +use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; +use nymsphinx_types::header::keys::RoutingKeys; +use nymsphinx_types::SharedSecret; +use std::sync::Arc; +use tokio::stream::StreamExt; +use tokio::time::{Duration, Error as TimeError}; + +type CachedKeys = (Option, RoutingKeys); + +pub(super) struct KeyCache { + vpn_key_cache: Arc>, + invalidator_sender: InvalidatorActionSender, + cache_entry_ttl: Duration, +} + +impl Drop for KeyCache { + fn drop(&mut self) { + debug!("dropping key cache"); + if self + .invalidator_sender + .unbounded_send(InvalidatorAction::Stop) + .is_err() + { + debug!("invalidator has already been dropped") + } + } +} + +impl KeyCache { + pub(super) fn new(cache_entry_ttl: Duration) -> Self { + let cache = Arc::new(DashMap::new()); + let (sender, receiver) = mpsc::unbounded(); + + let mut invalidator = CacheInvalidator { + entry_ttl: cache_entry_ttl, + vpn_key_cache: Arc::clone(&cache), + expirations: NonExhaustiveDelayQueue::new(), + action_receiver: receiver, + }; + + // TODO: is it possible to avoid tokio::spawn here and make it semi-runtime agnostic? + tokio::spawn(async move { invalidator.run().await }); + + KeyCache { + vpn_key_cache: cache, + invalidator_sender: sender, + cache_entry_ttl, + } + } + + pub(super) fn insert(&self, key: SharedSecret, cached_keys: CachedKeys) -> bool { + trace!("inserting {:?} into the cache", key); + let insertion_result = self.vpn_key_cache.insert(key.clone(), cached_keys); + if !insertion_result { + debug!("{:?} was put into the cache", key); + // this shouldn't really happen, but don't insert entry to invalidator if it was already + // in the cache + self.invalidator_sender + .unbounded_send(InvalidatorAction::Insert(key)) + .expect("Cache invalidator has crashed!"); + } + insertion_result + } + + // ElementGuard has Deref for CachedKeys so that's fine + pub(super) fn get(&self, key: &SharedSecret) -> Option> { + self.vpn_key_cache.get(key) + } + + pub(super) fn cache_entry_ttl(&self) -> Duration { + self.cache_entry_ttl + } + + #[cfg(test)] + pub(super) fn is_empty(&self) -> bool { + self.vpn_key_cache.is_empty() + } + + #[cfg(test)] + pub(super) fn len(&self) -> usize { + self.vpn_key_cache.len() + } +} + +enum InvalidatorAction { + Insert(SharedSecret), + Stop, +} + +type InvalidatorActionSender = mpsc::UnboundedSender; +type InvalidatorActionReceiver = mpsc::UnboundedReceiver; + +struct CacheInvalidator { + entry_ttl: Duration, + vpn_key_cache: Arc>, + expirations: NonExhaustiveDelayQueue, + action_receiver: InvalidatorActionReceiver, +} + +// we do not have a strong requirement of invalidating things EXACTLY after their TTL expires. +// we want them to be eventually gone in a relatively timely manner. +impl CacheInvalidator { + // two obvious ways I've seen of running this were as follows: + // + // 1) every X second, purge all expired entries + // pros: simpler to implement + // cons: will require to obtain write lock multiple times in quick succession + // + // 2) purge entry as soon as it expires + // pros: the lock situation will be spread more in time + // cons: possibly less efficient? + + fn handle_expired(&mut self, expired: Option, TimeError>>) { + let expired = expired.expect("the queue has unexpectedly terminated!"); + let expired_entry = expired.expect("Encountered timer issue within the runtime!"); + + debug!( + "{:?} has expired and will be removed", + expired_entry.get_ref() + ); + + if !self.vpn_key_cache.remove(&expired_entry.into_inner()) { + error!("Tried to remove vpn cache entry for non-existent key!") + } + } + + /// Handles received action. Return `bool` indicates whether the invalidator + /// should terminate. + fn handle_action(&mut self, action: Option) -> bool { + if action.is_none() { + return true; + } + + match action.unwrap() { + InvalidatorAction::Stop => true, + InvalidatorAction::Insert(shared_secret) => { + self.expirations.insert(shared_secret, self.entry_ttl); + false + } + } + } + + async fn run(&mut self) { + loop { + tokio::select! { + expired = self.expirations.next() => { + self.handle_expired(expired); + } + action = self.action_receiver.next() => { + if self.handle_action(action) { + info!("Stopping cache invalidator"); + return + } + } + + } + } + } +} diff --git a/common/mixnode-common/src/cached_packet_processor/error.rs b/common/mixnode-common/src/cached_packet_processor/error.rs new file mode 100644 index 00000000000..ed87dac37e7 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/error.rs @@ -0,0 +1,72 @@ +// 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::SURBAckRecoveryError; +use nymsphinx_addressing::nodes::NymNodeRoutingAddressError; +use nymsphinx_types::Error as SphinxError; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub enum MixProcessingError { + SphinxProcessingError(SphinxError), + InvalidHopAddress(NymNodeRoutingAddressError), + NoSURBAckInFinalHop, + MalformedSURBAck(SURBAckRecoveryError), +} + +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of SphinxError + fn from(err: SphinxError) -> Self { + use MixProcessingError::*; + + SphinxProcessingError(err) + } +} + +impl From for MixProcessingError { + fn from(err: NymNodeRoutingAddressError) -> Self { + use MixProcessingError::*; + + InvalidHopAddress(err) + } +} + +impl From for MixProcessingError { + fn from(err: SURBAckRecoveryError) -> Self { + use MixProcessingError::*; + + MalformedSURBAck(err) + } +} + +impl Display for MixProcessingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + MixProcessingError::SphinxProcessingError(sphinx_err) => { + write!(f, "Sphinx Processing Error - {}", sphinx_err) + } + MixProcessingError::InvalidHopAddress(address_err) => { + write!(f, "Invalid Hop Address - {:?}", address_err) + } + MixProcessingError::NoSURBAckInFinalHop => { + write!(f, "No SURBAck present in the final hop data") + } + MixProcessingError::MalformedSURBAck(surb_ack_err) => { + write!(f, "Malformed SURBAck - {:?}", surb_ack_err) + } + } + } +} + +impl std::error::Error for MixProcessingError {} diff --git a/common/mixnode-common/src/cached_packet_processor/mod.rs b/common/mixnode-common/src/cached_packet_processor/mod.rs new file mode 100644 index 00000000000..3bca3d76117 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/mod.rs @@ -0,0 +1,17 @@ +// 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. + +mod cache; +pub mod error; +pub mod processor; diff --git a/common/mixnode-common/src/cached_packet_processor/processor.rs b/common/mixnode-common/src/cached_packet_processor/processor.rs new file mode 100644 index 00000000000..c61f36eec25 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/processor.rs @@ -0,0 +1,483 @@ +// 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::cached_packet_processor::cache::KeyCache; +use crate::cached_packet_processor::error::MixProcessingError; +use log::*; +use nymsphinx_acknowledgements::surb_ack::SURBAck; +use nymsphinx_addressing::nodes::NymNodeRoutingAddress; +use nymsphinx_forwarding::packet::MixPacket; +use nymsphinx_framing::packet::FramedSphinxPacket; +use nymsphinx_params::{PacketMode, PacketSize}; +use nymsphinx_types::header::keys::RoutingKeys; +use nymsphinx_types::{ + Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey, + ProcessedPacket, SharedSecret, SphinxHeader, SphinxPacket, +}; +use std::convert::TryFrom; +use std::sync::Arc; +use tokio::time::Duration; + +type ForwardAck = MixPacket; +type CachedKeys = (Option, RoutingKeys); + +pub struct ProcessedFinalHop { + pub destination: DestinationAddressBytes, + pub forward_ack: Option, + pub message: Vec, +} + +pub enum MixProcessingResult { + ForwardHop(MixPacket), + FinalHop(ProcessedFinalHop), +} + +pub struct CachedPacketProcessor { + sphinx_key: Arc, + vpn_key_cache: KeyCache, +} + +impl CachedPacketProcessor { + /// Creates new instance of `CachedPacketProcessor` + pub fn new(sphinx_key: PrivateKey, cache_entry_ttl: Duration) -> Self { + CachedPacketProcessor { + sphinx_key: Arc::new(sphinx_key), + vpn_key_cache: KeyCache::new(cache_entry_ttl), + } + } + + /// Clones `self` without the `vpn_key_cache`. + pub fn clone_without_cache(&self) -> Self { + CachedPacketProcessor { + sphinx_key: self.sphinx_key.clone(), + vpn_key_cache: KeyCache::new(self.vpn_key_cache.cache_entry_ttl()), + } + } + + /// A naive way of delaying packet. + async fn delay_packet(&self, delay: SphinxDelay) { + // TODO: this should perhaps be replaced with a `DelayQueue` + tokio::time::delay_for(delay.to_duration()).await; + } + + /// Recomputes routing keys for the given initial secret. + fn recompute_routing_keys(&self, initial_secret: &SharedSecret) -> RoutingKeys { + SphinxHeader::compute_routing_keys(initial_secret, &self.sphinx_key) + } + + /// Performs a fresh sphinx unwrapping using no cache. + fn perform_initial_sphinx_packet_processing( + &self, + packet: SphinxPacket, + ) -> Result { + packet.process(&self.sphinx_key).map_err(|err| { + warn!("Failed to unwrap Sphinx packet: {:?}", err); + MixProcessingError::SphinxProcessingError(err) + }) + } + + /// Unwraps sphinx packet using already cached keys. + fn perform_initial_sphinx_packet_processing_with_cached_keys( + &self, + packet: SphinxPacket, + keys: &CachedKeys, + ) -> Result { + packet + .process_with_derived_keys(&keys.0, &keys.1) + .map_err(|err| { + warn!("Failed to unwrap Sphinx packet: {:?}", err); + MixProcessingError::SphinxProcessingError(err) + }) + } + + /// Stores the keys corresponding to the packet that was just processed. + fn cache_keys(&self, initial_secret: SharedSecret, processed_packet: &ProcessedPacket) { + let new_shared_secret = processed_packet.shared_secret(); + let routing_keys = self.recompute_routing_keys(&initial_secret); + if self + .vpn_key_cache + .insert(initial_secret, (new_shared_secret, routing_keys)) + { + debug!("Other thread has already cached keys for this secret!") + } + } + + /// Takes the received framed packet and tries to unwrap it from the sphinx encryption. + /// For any vpn packets it will try to re-use cached keys and if none are available, + /// after first processing, the keys are going to get cached. + fn perform_initial_unwrapping( + &self, + received: FramedSphinxPacket, + ) -> Result { + let packet_mode = received.packet_mode(); + let sphinx_packet = received.into_inner(); + let initial_secret = sphinx_packet.shared_secret(); + + // try to use pre-computed keys only for the vpn-packets + if packet_mode.is_vpn() { + if let Some(cached_keys) = self.vpn_key_cache.get(&initial_secret) { + return self.perform_initial_sphinx_packet_processing_with_cached_keys( + sphinx_packet, + cached_keys.value(), + ); + } + } + + let processing_result = self.perform_initial_sphinx_packet_processing(sphinx_packet); + // quicker exit because this will be the most common case + if !packet_mode.is_vpn() { + return processing_result; + } + + if let Ok(processed_packet) = processing_result.as_ref() { + // if we managed to process packet we saw for the first time AND it's a vpn packet + // cache the keys + self.cache_keys(initial_secret, processed_packet); + } + processing_result + } + + /// Processed received forward hop packet - tries to extract next hop address, delays it + /// if it was not a vpn packet and packs all the data in a way that can be easily sent + /// to the next hop. + async fn process_forward_hop( + &self, + packet: SphinxPacket, + forward_address: NodeAddressBytes, + delay: SphinxDelay, + packet_mode: PacketMode, + ) -> Result { + let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; + + if !packet_mode.is_vpn() { + self.delay_packet(delay).await; + } + + let mix_packet = MixPacket::new(next_hop_address, packet, packet_mode); + Ok(MixProcessingResult::ForwardHop(mix_packet)) + } + + /// Split data extracted from the final hop sphinx packet into a SURBAck and message + /// that should get delivered to a client. + fn split_hop_data_into_ack_and_message( + &self, + mut extracted_data: Vec, + ) -> Result<(Vec, Vec), MixProcessingError> { + // in theory it's impossible for this to fail since it managed to go into correct `match` + // branch at the caller + if extracted_data.len() < SURBAck::len() { + return Err(MixProcessingError::NoSURBAckInFinalHop); + } + + let message = extracted_data.split_off(SURBAck::len()); + let ack_data = extracted_data; + Ok((ack_data, message)) + } + + /// Tries to extract a SURBAck that could be sent back into the mix network and message + /// that should get delivered to a client from received Sphinx packet. + fn split_into_ack_and_message( + &self, + data: Vec, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result<(Option, Vec), MixProcessingError> { + match packet_size { + PacketSize::ACKPacket => { + trace!("received an ack packet!"); + Ok((None, data)) + } + PacketSize::RegularPacket | PacketSize::ExtendedPacket => { + trace!("received a normal packet!"); + let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?; + let (ack_first_hop, ack_packet) = SURBAck::try_recover_first_hop_packet(&ack_data)?; + let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_mode); + Ok((Some(forward_ack), message)) + } + } + } + + /// Processed received final hop packet - tries to extract SURBAck out of it (assuming the + /// packet itself is not an ACK) and splits it from the message that should get delivered + /// to the destination. + fn process_final_hop( + &self, + destination: DestinationAddressBytes, + payload: Payload, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result { + let packet_message = payload.recover_plaintext()?; + + let (forward_ack, message) = + self.split_into_ack_and_message(packet_message, packet_size, packet_mode)?; + + Ok(MixProcessingResult::FinalHop(ProcessedFinalHop { + destination, + forward_ack, + message, + })) + } + + /// Performs final processing for the unwrapped packet based on whether it was a forward hop + /// or a final hop. + async fn perform_final_processing( + &self, + packet: ProcessedPacket, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result { + match packet { + ProcessedPacket::ForwardHop(packet, address, delay) => { + self.process_forward_hop(packet, address, delay, packet_mode) + .await + } + // right now there's no use for the surb_id included in the header - probably it should get removed from the + // sphinx all together? + ProcessedPacket::FinalHop(destination, _, payload) => { + self.process_final_hop(destination, payload, packet_size, packet_mode) + } + } + } + + pub async fn process_received( + &self, + received: FramedSphinxPacket, + ) -> Result { + // explicit packet size will help to correctly parse final hop + let packet_size = received.packet_size(); + let packet_mode = received.packet_mode(); + + // unwrap the sphinx packet and if possible and appropriate, cache keys + let processed_packet = self.perform_initial_unwrapping(received)?; + + // for forward, non-vpn packets delay for specified amount, + // for final packets, extract SURBAck + self.perform_final_processing(processed_packet, packet_size, packet_mode) + .await + } +} + +// TODO: what more could we realistically test here? +#[cfg(test)] +mod tests { + use super::*; + use nymsphinx_types::builder::SphinxPacketBuilder; + use nymsphinx_types::crypto::keygen; + use nymsphinx_types::{ + Destination, Node, PublicKey, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, + }; + use std::convert::TryInto; + use std::net::SocketAddr; + + fn fixture() -> CachedPacketProcessor { + let local_keys = keygen(); + CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)) + } + + fn make_valid_final_sphinx_packet(size: PacketSize, public_key: PublicKey) -> SphinxPacket { + let routing_address: NymNodeRoutingAddress = + NymNodeRoutingAddress::from("127.0.0.1:1789".parse::().unwrap()); + + let node = Node::new(routing_address.try_into().unwrap(), public_key); + + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + + // required until https://github.com/nymtech/sphinx/issues/71 is fixed + let dummy_delay = SphinxDelay::new_from_nanos(42); + + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &[node], &destination, &[dummy_delay]) + .unwrap() + } + + fn make_valid_forward_sphinx_packet(size: PacketSize, public_key: PublicKey) -> SphinxPacket { + let routing_address: NymNodeRoutingAddress = + NymNodeRoutingAddress::from("127.0.0.1:1789".parse::().unwrap()); + + let some_node_key = keygen(); + let route = [ + Node::new(routing_address.try_into().unwrap(), public_key), + Node::new(routing_address.try_into().unwrap(), some_node_key.1), + ]; + + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + + let delays = [ + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + ]; + + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &route, &destination, &delays) + .unwrap() + } + + #[tokio::test] + async fn recomputing_routing_keys_derives_correct_set_of_keys() { + let processor = fixture(); + let (_, initial_secret) = keygen(); + assert_eq!( + processor.recompute_routing_keys(&initial_secret), + SphinxHeader::compute_routing_keys(&initial_secret, &processor.sphinx_key) + ) + } + + #[tokio::test] + async fn caching_keys_updates_local_state_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let initial_secret = final_hop.shared_secret(); + let processed = final_hop.process(&processor.sphinx_key).unwrap(); + + processor.cache_keys(initial_secret.clone(), &processed); + let cache_entry = processor.vpn_key_cache.get(&initial_secret).unwrap(); + + let (cached_secret, cached_routing_keys) = cache_entry.value(); + + assert!(cached_secret.is_none()); + let recomputed_keys = processor.recompute_routing_keys(&initial_secret); + // if one key matches then all keys must match (or there is a serious bug inside sphinx) + assert_eq!( + cached_routing_keys.stream_cipher_key, + recomputed_keys.stream_cipher_key + ); + } + + #[tokio::test] + async fn caching_keys_updates_local_state_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let initial_secret = forward_hop.shared_secret(); + let processed = forward_hop.process(&processor.sphinx_key).unwrap(); + + processor.cache_keys(initial_secret.clone(), &processed); + let cache_entry = processor.vpn_key_cache.get(&initial_secret).unwrap(); + + let (cached_secret, cached_routing_keys) = cache_entry.value(); + + assert_eq!( + cached_secret.as_ref().unwrap(), + processed.shared_secret().as_ref().unwrap() + ); + let recomputed_keys = processor.recompute_routing_keys(&initial_secret); + // if one key matches then all keys must match (or there is a serious bug inside sphinx) + assert_eq!( + cached_routing_keys.stream_cipher_key, + recomputed_keys.stream_cipher_key + ); + } + + #[tokio::test] + async fn performing_initial_unwrapping_caches_keys_if_vpnmode_used_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(final_hop, PacketMode::VPN); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert_eq!(processor.vpn_key_cache.len(), 1); + } + + #[tokio::test] + async fn performing_initial_unwrapping_caches_keys_if_vpnmode_used_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(forward_hop, PacketMode::VPN); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert_eq!(processor.vpn_key_cache.len(), 1); + } + + #[tokio::test] + async fn performing_initial_unwrapping_does_no_caching_for_mix_mode_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(final_hop, PacketMode::Mix); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert!(processor.vpn_key_cache.is_empty()); + } + + #[tokio::test] + async fn performing_initial_unwrapping_does_no_caching_for_mix_mode_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(forward_hop, PacketMode::Mix); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert!(processor.vpn_key_cache.is_empty()); + } + + #[tokio::test] + async fn splitting_hop_data_works_for_sufficiently_long_payload() { + let processor = fixture(); + + let short_data = vec![42u8]; + assert!(processor + .split_hop_data_into_ack_and_message(short_data) + .is_err()); + + let sufficient_data = vec![42u8; SURBAck::len()]; + let (ack, data) = processor + .split_hop_data_into_ack_and_message(sufficient_data.clone()) + .unwrap(); + assert_eq!(sufficient_data, ack); + assert!(data.is_empty()); + + let long_data = vec![42u8; SURBAck::len() * 5]; + let (ack, data) = processor + .split_hop_data_into_ack_and_message(long_data.clone()) + .unwrap(); + assert_eq!(ack.len(), SURBAck::len()); + assert_eq!(data.len(), SURBAck::len() * 4) + } + + #[tokio::test] + async fn splitting_into_ack_and_message_returns_whole_data_for_ack() { + let processor = fixture(); + + let data = vec![42u8; SURBAck::len() + 10]; + let (ack, message) = processor + .split_into_ack_and_message(data.clone(), PacketSize::ACKPacket, Default::default()) + .unwrap(); + assert!(ack.is_none()); + assert_eq!(data, message) + } +} diff --git a/common/mixnode-common/src/lib.rs b/common/mixnode-common/src/lib.rs new file mode 100644 index 00000000000..2b65a961b77 --- /dev/null +++ b/common/mixnode-common/src/lib.rs @@ -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 cached_packet_processor; diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml new file mode 100644 index 00000000000..65dea23278c --- /dev/null +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "nonexhaustive-delayqueue" +version = "0.1.0" +authors = ["Jędrzej Stuczyński "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "0.2", features = ["stream", "time"] } \ No newline at end of file diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs b/common/nonexhaustive-delayqueue/src/lib.rs similarity index 69% rename from clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs rename to common/nonexhaustive-delayqueue/src/lib.rs index a27dc9c6a2c..b2903546e9e 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs +++ b/common/nonexhaustive-delayqueue/src/lib.rs @@ -12,32 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::Stream; use std::pin::Pin; use std::task::{Context, Poll, Waker}; use std::time::Duration; -use tokio::time::{ - delay_queue::{self, Expired}, - DelayQueue, -}; +use tokio::stream::Stream; +pub use tokio::time::delay_queue::Expired; +use tokio::time::{delay_queue, DelayQueue}; -// works under assumption that it will be used inside a loop, where we never want a `None` -// TODO: perhaps this should/could be renamed and moved to common/utils (and expose all inner methods?) -pub struct AckDelayQueue { +pub type QueueKey = delay_queue::Key; + +/// A variant of tokio's `DelayQueue`, such that its `Stream` implementation will never return a 'None'. +pub struct NonExhaustiveDelayQueue { inner: DelayQueue, waker: Option, } // more methods of underlying DelayQueue will get exposed as we need them -impl AckDelayQueue { +impl NonExhaustiveDelayQueue { pub fn new() -> Self { - AckDelayQueue { + NonExhaustiveDelayQueue { inner: DelayQueue::new(), waker: None, } } - pub fn insert(&mut self, value: T, timeout: Duration) -> delay_queue::Key { + pub fn insert(&mut self, value: T, timeout: Duration) -> QueueKey { let key = self.inner.insert(value, timeout); if let Some(waker) = self.waker.take() { // we were waiting for an item - wake the executor! @@ -46,12 +45,14 @@ impl AckDelayQueue { key } - pub fn remove(&mut self, key: &delay_queue::Key) -> Expired { + // TODO: it seems like this one can cause panic in very rare edge cases, however, + // I can't seem to be able to reproduce it at all. + pub fn remove(&mut self, key: &QueueKey) -> Expired { self.inner.remove(key) } } -impl Stream for AckDelayQueue { +impl Stream for NonExhaustiveDelayQueue { type Item = as Stream>::Item; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -66,3 +67,10 @@ impl Stream for AckDelayQueue { } } } + +// #[cfg(test)] +// mod tests { +// use super::*; +// +// +// } diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index be7b5b58315..083a4d8833c 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -15,6 +15,7 @@ nymsphinx-addressing = { path = "addressing" } nymsphinx-anonymous-replies = { path = "anonymous-replies" } nymsphinx-chunking = { path = "chunking" } nymsphinx-cover = { path = "cover" } +nymsphinx-forwarding = { path = "forwarding" } nymsphinx-params = { path = "params" } nymsphinx-types = { path = "types" } @@ -26,4 +27,8 @@ topology = { path = "../topology" } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nymsphinx-framing] -path = "framing" \ No newline at end of file +path = "framing" + +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] +version = "0.2" +features = ["sync"] diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index 8722aa0ac60..d2f2b5d8b5d 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -21,7 +21,7 @@ use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; use nymsphinx_types::builder::SphinxPacketBuilder; use nymsphinx_types::{ delays::{self, Delay}, - SphinxPacket, + EphemeralSecret, SphinxPacket, }; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; @@ -50,6 +50,7 @@ impl SURBAck { marshaled_fragment_id: [u8; 5], average_delay: time::Duration, topology: &NymTopology, + initial_sphinx_secret: Option<&EphemeralSecret>, ) -> Result where R: RngCore + CryptoRng, @@ -61,9 +62,13 @@ impl SURBAck { 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()) + let mut surb_builder = + SphinxPacketBuilder::new().with_payload_size(PacketSize::ACKPacket.payload_size()); + if let Some(initial_secret) = initial_sphinx_secret { + surb_builder = surb_builder.with_initial_secret(initial_secret); + } + + let surb_ack_packet = surb_builder .build_packet(surb_ack_payload, &route, &destination, &delays) .unwrap(); diff --git a/common/nymsphinx/cover/Cargo.toml b/common/nymsphinx/cover/Cargo.toml index d9f5846dfda..49d58431c65 100644 --- a/common/nymsphinx/cover/Cargo.toml +++ b/common/nymsphinx/cover/Cargo.toml @@ -14,5 +14,6 @@ nymsphinx-acknowledgements = { path = "../acknowledgements" } nymsphinx-addressing = { path = "../addressing" } nymsphinx-chunking = { path = "../chunking" } nymsphinx-params = { path = "../params" } +nymsphinx-forwarding = { path = "../forwarding" } nymsphinx-types = { path = "../types" } topology = { path = "../../topology" } \ No newline at end of file diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 1996f444757..ef80eabbb4e 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -19,10 +19,13 @@ use nymsphinx_acknowledgements::AckKey; use nymsphinx_addressing::clients::Recipient; use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nymsphinx_chunking::fragment::COVER_FRAG_ID; +use nymsphinx_forwarding::packet::MixPacket; use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, DEFAULT_NUM_MIX_HOPS}; +use nymsphinx_params::{ + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, DEFAULT_NUM_MIX_HOPS, +}; use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Error as SphinxError, SphinxPacket}; +use nymsphinx_types::{delays, Error as SphinxError}; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; use std::time; @@ -74,6 +77,7 @@ where COVER_FRAG_ID.to_bytes(), average_ack_delay, topology, + None, )?) } @@ -84,7 +88,7 @@ pub fn generate_loop_cover_packet( full_address: &Recipient, average_ack_delay: time::Duration, average_packet_delay: time::Duration, -) -> Result<(NymNodeRoutingAddress, SphinxPacket), CoverMessageError> +) -> Result where R: RngCore + CryptoRng, { @@ -144,7 +148,8 @@ where let first_hop_address = NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap(); - Ok((first_hop_address, packet)) + // if client is running in vpn mode, he won't even be sending cover traffic + Ok(MixPacket::new(first_hop_address, packet, PacketMode::Mix)) } /// Helper function used to determine if given message represents a loop cover message. diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml new file mode 100644 index 00000000000..80ab0536651 --- /dev/null +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nymsphinx-forwarding" +version = "0.1.0" +authors = ["Jedrzej Stuczynski "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nymsphinx-addressing = { path = "../addressing" } +nymsphinx-params = { path = "../params" } +nymsphinx-types = { path = "../types" } diff --git a/common/nymsphinx/forwarding/src/lib.rs b/common/nymsphinx/forwarding/src/lib.rs new file mode 100644 index 00000000000..f85c8e93622 --- /dev/null +++ b/common/nymsphinx/forwarding/src/lib.rs @@ -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; diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs new file mode 100644 index 00000000000..fbfec653ad2 --- /dev/null +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -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 nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nymsphinx_params::{PacketMode, PacketSize}; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub enum MixPacketFormattingError { + TooFewBytesProvided, + InvalidPacketMode, + InvalidPacketSize(usize), + InvalidAddress, + MalformedSphinxPacket, +} + +impl Display for MixPacketFormattingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use MixPacketFormattingError::*; + match self { + TooFewBytesProvided => write!(f, "Too few bytes provided to recover from bytes"), + InvalidAddress => write!(f, "address field was incorrectly encoded"), + InvalidPacketSize(actual) => + write!( + f, + "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"), + InvalidPacketMode => write!(f, "provided packet mode is invalid") + } + } +} + +impl std::error::Error for MixPacketFormattingError {} + +impl From for MixPacketFormattingError { + fn from(_: NymNodeRoutingAddressError) -> Self { + MixPacketFormattingError::InvalidAddress + } +} + +pub struct MixPacket { + next_hop: NymNodeRoutingAddress, + sphinx_packet: SphinxPacket, + packet_mode: PacketMode, +} + +impl MixPacket { + pub fn new( + next_hop: NymNodeRoutingAddress, + sphinx_packet: SphinxPacket, + packet_mode: PacketMode, + ) -> Self { + MixPacket { + next_hop, + sphinx_packet, + packet_mode, + } + } + + pub fn next_hop(&self) -> NymNodeRoutingAddress { + self.next_hop + } + + pub fn sphinx_packet(&self) -> &SphinxPacket { + &self.sphinx_packet + } + + pub fn into_sphinx_packet(self) -> SphinxPacket { + self.sphinx_packet + } + + pub fn packet_mode(&self) -> PacketMode { + self.packet_mode + } + + // the message is formatted as follows: + // PACKET_MODE || FIRST_HOP || SPHINX_PACKET + pub fn try_from_bytes(b: &[u8]) -> Result { + let packet_mode = match PacketMode::try_from(b[0]) { + Ok(mode) => mode, + Err(_) => return Err(MixPacketFormattingError::InvalidPacketMode), + }; + + let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?; + let addr_offset = next_hop.bytes_min_len(); + + let sphinx_packet_data = &b[addr_offset + 1..]; + let packet_size = sphinx_packet_data.len(); + if PacketSize::get_type(packet_size).is_err() { + Err(MixPacketFormattingError::InvalidPacketSize(packet_size)) + } else { + let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) { + Ok(packet) => packet, + Err(_) => return Err(MixPacketFormattingError::MalformedSphinxPacket), + }; + + Ok(MixPacket { + next_hop, + sphinx_packet, + packet_mode, + }) + } + } + + pub fn into_bytes(self) -> Vec { + std::iter::once(self.packet_mode as u8) + .chain(self.next_hop.as_bytes().into_iter()) + .chain(self.sphinx_packet.to_bytes().into_iter()) + .collect() + } +} + +// TODO: test for serialization and errors! diff --git a/common/nymsphinx/framing/src/codec.rs b/common/nymsphinx/framing/src/codec.rs new file mode 100644 index 00000000000..a9f21a9f80c --- /dev/null +++ b/common/nymsphinx/framing/src/codec.rs @@ -0,0 +1,339 @@ +// 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::packet::{FramedSphinxPacket, Header}; +use bytes::{Buf, BufMut, BytesMut}; +use nymsphinx_params::packet_modes::InvalidPacketMode; +use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; +use std::io; +use tokio_util::codec::{Decoder, Encoder}; + +#[derive(Debug)] +pub enum SphinxCodecError { + InvalidPacketSize, + InvalidPacketMode, + MalformedSphinxPacket, + IoError(io::Error), +} + +impl From for SphinxCodecError { + fn from(err: io::Error) -> Self { + SphinxCodecError::IoError(err) + } +} + +impl Into for SphinxCodecError { + fn into(self) -> io::Error { + match self { + SphinxCodecError::InvalidPacketSize => { + io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") + } + SphinxCodecError::InvalidPacketMode => { + io::Error::new(io::ErrorKind::InvalidInput, "invalid packet mode") + } + SphinxCodecError::MalformedSphinxPacket => { + io::Error::new(io::ErrorKind::InvalidData, "malformed packet") + } + SphinxCodecError::IoError(err) => err, + } + } +} + +impl From for SphinxCodecError { + fn from(_: InvalidPacketSize) -> Self { + SphinxCodecError::InvalidPacketSize + } +} + +impl From for SphinxCodecError { + fn from(_: InvalidPacketMode) -> Self { + SphinxCodecError::InvalidPacketMode + } +} + +// TODO: in the future it could be extended to have state containing symmetric encryption key +// so that all data could be encrypted easily (alternatively we could just slap TLS) +pub struct SphinxCodec; + +impl Encoder for SphinxCodec { + type Error = SphinxCodecError; + + fn encode(&mut self, item: FramedSphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { + item.header.encode(dst)?; + dst.put(item.packet.to_bytes().as_ref()); + Ok(()) + } +} + +impl Decoder for SphinxCodec { + type Item = FramedSphinxPacket; + type Error = SphinxCodecError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + // can't do anything if we have no bytes, but let's reserve enough for the most + // conservative case, i.e. receiving an ack packet + src.reserve(Header::SIZE + PacketSize::ACKPacket.size()); + return Ok(None); + } + + // because header is so small and simple it makes no point in trying to cache + // this result. It will be just simpler to re-decode it + let header = match Header::decode(src)? { + Some(header) => header, + None => return Ok(None), // we have some data but not enough to get header back + }; + + let sphinx_packet_size = header.packet_size.size(); + let frame_len = Header::SIZE + sphinx_packet_size; + + if src.len() < frame_len { + // we don't have enough bytes to read the rest of frame + src.reserve(sphinx_packet_size); + return Ok(None); + } + + // advance buffer past the header - at this point we have enough bytes + src.advance(Header::SIZE); + let sphinx_packet_bytes = src.split_to(sphinx_packet_size); + let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { + Ok(sphinx_packet) => sphinx_packet, + // here it could be debatable whether stream is corrupt or not, + // but let's go with the safer approach and assume it is. + Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), + }; + + let nymsphinx_packet = FramedSphinxPacket { + header, + packet: sphinx_packet, + }; + + // As per docs: + // Before returning from the function, implementations should ensure that the buffer + // has appropriate capacity in anticipation of future calls to decode. + // Failing to do so leads to inefficiency. + + // if we have at least one more byte available, we can reserve enough bytes for + // the entire next frame, if not, we assume the next frame is an ack packet and + // reserve for that. + if !src.is_empty() { + let next_packet_len = match PacketSize::try_from(src[0]) { + Ok(next_packet_len) => next_packet_len, + // the next frame will be malformed but let's leave handling the error to the next + // call to 'decode', as presumably, the current sphinx packet is still valid + Err(_) => return Ok(Some(nymsphinx_packet)), + }; + let next_frame_len = next_packet_len.size() + Header::SIZE; + src.reserve(next_frame_len - 1); + } else { + src.reserve(Header::SIZE + PacketSize::ACKPacket.size()); + } + + Ok(Some(nymsphinx_packet)) + } +} + +#[cfg(test)] +mod packet_encoding { + use super::*; + use nymsphinx_types::builder::SphinxPacketBuilder; + use nymsphinx_types::{ + crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, + DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, + }; + + fn make_valid_sphinx_packet(size: PacketSize) -> SphinxPacket { + let (_, node1_pk) = crypto::keygen(); + let node1 = Node::new( + NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), + node1_pk, + ); + let (_, node2_pk) = crypto::keygen(); + let node2 = Node::new( + NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]), + node2_pk, + ); + let (_, node3_pk) = crypto::keygen(); + let node3 = Node::new( + NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), + node3_pk, + ); + + let route = [node1, node2, node3]; + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + let delays = vec![ + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + ]; + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &route, &destination, &delays) + .unwrap() + } + + #[test] + fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() { + let header = Default::default(); + let sphinx_packet = make_valid_sphinx_packet(Default::default()); + let sphinx_bytes = sphinx_packet.to_bytes(); + + let packet = FramedSphinxPacket { + header, + packet: sphinx_packet, + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(packet, &mut bytes).unwrap(); + let decoded = SphinxCodec.decode(&mut bytes).unwrap().unwrap(); + + assert_eq!(decoded.header, header); + assert_eq!(decoded.packet.to_bytes(), sphinx_bytes) + } + + #[cfg(test)] + mod decode_will_allocate_enough_bytes_for_next_call { + use super::*; + + #[test] + fn for_empty_bytes() { + // empty bytes should allocate for header + ack packet + let mut empty_bytes = BytesMut::new(); + assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none()); + assert_eq!( + empty_bytes.capacity(), + Header::SIZE + PacketSize::ACKPacket.size() + ); + } + + #[test] + fn for_bytes_with_header() { + // if header gets decoded there should be enough bytes for the entire frame + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + for packet_size in packet_sizes { + let header = Header { + packet_size, + packet_mode: Default::default(), + }; + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + + assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size()) + } + } + + #[test] + fn for_full_frame() { + // if full frame is used exactly, there should be enough space for header + ack packet + let packet = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(packet, &mut bytes).unwrap(); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert_eq!( + bytes.capacity(), + Header::SIZE + PacketSize::ACKPacket.size() + ); + } + + #[test] + fn for_full_frame_with_extra_byte() { + // if there was at least 1 byte left, there should be enough space for entire next frame + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + + for packet_size in packet_sizes { + let first_packet = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(first_packet, &mut bytes).unwrap(); + bytes.put_u8(packet_size as u8); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + + assert!(bytes.capacity() >= Header::SIZE + packet_size.size()) + } + } + } + + #[test] + fn can_decode_two_packets_immediately() { + let packet1 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let packet2 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + + SphinxCodec.encode(packet1, &mut bytes).unwrap(); + SphinxCodec.encode(packet2, &mut bytes).unwrap(); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + } + + #[test] + fn can_decode_two_packets_in_separate_calls() { + let packet1 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let packet2 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + let mut bytes_tmp = BytesMut::new(); + + SphinxCodec.encode(packet1, &mut bytes).unwrap(); + SphinxCodec.encode(packet2, &mut bytes_tmp).unwrap(); + + let tmp = bytes_tmp.split_off(100); + bytes.put(bytes_tmp); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + + bytes.put(tmp); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + } +} diff --git a/common/nymsphinx/framing/src/lib.rs b/common/nymsphinx/framing/src/lib.rs index 065c087f11a..4215f888015 100644 --- a/common/nymsphinx/framing/src/lib.rs +++ b/common/nymsphinx/framing/src/lib.rs @@ -12,136 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bytes::{Buf, BufMut, BytesMut}; -use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; -use nymsphinx_types::SphinxPacket; -use std::convert::TryFrom; -use std::io; -use tokio_util::codec::{Decoder, Encoder}; - -#[derive(Debug)] -pub enum SphinxCodecError { - InvalidPacketSize, - MalformedSphinxPacket, - IoError(io::Error), -} - -impl From for SphinxCodecError { - fn from(err: io::Error) -> Self { - SphinxCodecError::IoError(err) - } -} - -impl Into for SphinxCodecError { - fn into(self) -> io::Error { - match self { - SphinxCodecError::InvalidPacketSize => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") - } - SphinxCodecError::MalformedSphinxPacket => { - io::Error::new(io::ErrorKind::InvalidData, "malformed packet") - } - SphinxCodecError::IoError(err) => err, - } - } -} - -impl From for SphinxCodecError { - fn from(_: InvalidPacketSize) -> Self { - SphinxCodecError::InvalidPacketSize - } -} - -// The SphinxCodec is an extremely simple one, u8 representing one of valid packet -// lengths followed by the actual framed packet -pub struct SphinxCodec; - -impl Encoder for SphinxCodec { - type Error = SphinxCodecError; - - fn encode(&mut self, item: SphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { - let packet_bytes = item.to_bytes(); - let packet_length = packet_bytes.len(); - let packet_size = PacketSize::get_type(packet_length)?; - dst.reserve(1 + packet_size.size()); - dst.put_u8(packet_size as u8); - dst.put(packet_bytes.as_ref()); - Ok(()) - } -} - -impl Decoder for SphinxCodec { - type Item = SphinxPacket; - type Error = SphinxCodecError; - - //https://docs.rs/tokio-util/0.3.1/tokio_util/codec/trait.Decoder.html - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if src.is_empty() { - // can't do anything if we have no bytes - return Ok(None); - } - // we at least have a single byte in the buffer, so we can read the expected - // length of the sphinx packet - let packet_len_flag = src[0]; - let packet_len = PacketSize::try_from(packet_len_flag)?; - - let frame_len = packet_len.size() + 1; // one is due to the flag taking the space - if src.len() < frame_len { - // we don't have enough bytes to read the entire frame - src.reserve(frame_len); - return Ok(None); - } - // we advance the buffer beyond the flag - src.advance(1); - let sphinx_packet_bytes = src.split_to(packet_len.size()); - let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { - Ok(sphinx_packet) => sphinx_packet, - // here it could be debatable whether stream is corrupt or not, - // but let's go with the safer approach and assume it is. - Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), - }; - - // As per docs: - // Before returning from the function, implementations should ensure that the buffer - // has appropriate capacity in anticipation of future calls to decode. - // Failing to do so leads to inefficiency. - - // if we have at least one more byte available, we can reserve enough bytes for - // the entire next frame - if !src.is_empty() { - let next_packet_len = match PacketSize::try_from(src[0]) { - Ok(next_packet_len) => next_packet_len, - // the next frame will be malformed but let's leave handling the error to the next - // call to 'decode', as presumably, the current sphinx packet is still valid - Err(_) => return Ok(Some(sphinx_packet)), - }; - let next_frame_len = next_packet_len.size() + 1; - src.reserve(next_frame_len); - } - - Ok(Some(sphinx_packet)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(test)] - #[allow(dead_code)] - fn consume( - codec: &mut SphinxCodec, - bytes: &mut BytesMut, - ) -> Vec, SphinxCodecError>> { - let mut result = Vec::new(); - loop { - match codec.decode(bytes) { - Ok(None) => { - break; - } - output => result.push(output), - } - } - result - } -} +pub mod codec; +pub mod packet; diff --git a/common/nymsphinx/framing/src/packet.rs b/common/nymsphinx/framing/src/packet.rs new file mode 100644 index 00000000000..1296f5e75ba --- /dev/null +++ b/common/nymsphinx/framing/src/packet.rs @@ -0,0 +1,165 @@ +// 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::codec::SphinxCodecError; +use bytes::{BufMut, BytesMut}; +use nymsphinx_params::packet_sizes::PacketSize; +use nymsphinx_params::PacketMode; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; + +pub struct FramedSphinxPacket { + /// Contains any metadata helping receiver to handle the underlying packet. + pub(crate) header: Header, + + /// The actual SphinxPacket being sent. + pub(crate) packet: SphinxPacket, +} + +impl FramedSphinxPacket { + pub fn new(packet: SphinxPacket, packet_mode: PacketMode) -> Self { + // If this fails somebody is using the library in a super incorrect way, because they + // already managed to somehow create a sphinx packet + let packet_size = PacketSize::get_type(packet.len()).unwrap(); + FramedSphinxPacket { + header: Header { + packet_size, + packet_mode, + }, + packet, + } + } + + pub fn packet_size(&self) -> PacketSize { + self.header.packet_size + } + + pub fn packet_mode(&self) -> PacketMode { + self.header.packet_mode + } + + pub fn into_inner(self) -> SphinxPacket { + self.packet + } +} + +// Contains any metadata that might be useful for sending between mix nodes. +// TODO: in theory all those data could be put in a single `u8` by setting appropriate bits, +// but would that really be worth it? +#[derive(Debug, Default, PartialEq, Copy, Clone)] +pub struct Header { + /// Represents type and consequently size of the included SphinxPacket. + pub(crate) packet_size: PacketSize, + + /// Represents whether this packet is sent in a `vpn_mode` meaning it should not get delayed + /// and shared keys might get reused. Mixnodes are capable of inferring this mode from the + /// delay values inside the packet header (i.e. being set to 0), however, gateway, being final + /// hop, would be unable to do so. + /// + /// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues? + /// (note: this will be behind some encryption, either something implemented by us or some SSL action) + pub(crate) packet_mode: PacketMode, +} + +impl Header { + pub(crate) const SIZE: usize = 2; + + pub(crate) fn encode(&self, dst: &mut BytesMut) -> Result<(), SphinxCodecError> { + // we reserve one byte for `packet_size` and the other for `mode` + dst.reserve(Self::SIZE); + dst.put_u8(self.packet_size as u8); + dst.put_u8(self.packet_mode as u8); + // reserve bytes for the actual packet + dst.reserve(self.packet_size.size()); + Ok(()) + } + + pub(crate) fn decode(src: &mut BytesMut) -> Result, SphinxCodecError> { + if src.len() < Self::SIZE { + // can't do anything if we don't have enough bytes - but reserve enough for the next call + src.reserve(Self::SIZE); + return Ok(None); + } + + Ok(Some(Header { + packet_size: PacketSize::try_from(src[0])?, + packet_mode: PacketMode::try_from(src[1])?, + })) + } +} + +#[cfg(test)] +mod header_encoding { + use super::*; + + #[test] + fn header_can_be_decoded_from_a_valid_encoded_instance() { + let header = Header::default(); + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + let decoded = Header::decode(&mut bytes).unwrap().unwrap(); + assert_eq!(decoded, header); + } + + #[test] + fn decoding_will_fail_for_unknown_packet_size() { + let unknown_packet_size: u8 = 255; + // make sure this is still 'unknown' for if we make changes in the future + assert!(PacketSize::try_from(unknown_packet_size).is_err()); + + let mut bytes = BytesMut::from([unknown_packet_size, PacketMode::default() as u8].as_ref()); + assert!(Header::decode(&mut bytes).is_err()) + } + + #[test] + fn decoding_will_fail_for_unknown_packet_mode() { + let unknown_packet_mode: u8 = 255; + // make sure this is still 'unknown' for if we make changes in the future + assert!(PacketMode::try_from(unknown_packet_mode).is_err()); + + let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_mode].as_ref()); + assert!(Header::decode(&mut bytes).is_err()) + } + + #[test] + fn decode_will_allocate_enough_bytes_for_next_call() { + let mut empty_bytes = BytesMut::new(); + let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap(); + assert!(decode_attempt_1.is_none()); + assert!(empty_bytes.capacity() > Header::SIZE); + + let mut empty_bytes = BytesMut::with_capacity(1); + let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap(); + assert!(decode_attempt_2.is_none()); + assert!(empty_bytes.capacity() > Header::SIZE); + } + + #[test] + fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet() { + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + for packet_size in packet_sizes { + let header = Header { + packet_size, + packet_mode: Default::default(), + }; + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + assert_eq!(bytes.capacity(), bytes.len() + packet_size.size()) + } + } +} diff --git a/common/nymsphinx/params/src/lib.rs b/common/nymsphinx/params/src/lib.rs index afcdcda36d9..9c014581100 100644 --- a/common/nymsphinx/params/src/lib.rs +++ b/common/nymsphinx/params/src/lib.rs @@ -11,12 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use crypto::aes_ctr::Aes128Ctr; use crypto::blake3; -pub mod packet_sizes; -use crypto::aes_ctr::Aes128Ctr; +// Re-export for ease of use +pub use packet_modes::PacketMode; pub use packet_sizes::PacketSize; +pub mod packet_modes; +pub mod packet_sizes; + // If somebody can provide an argument why it might be reasonable to have more than 255 mix hops, // I will change this to [`usize`] pub const DEFAULT_NUM_MIX_HOPS: u8 = 3; diff --git a/common/nymsphinx/params/src/packet_modes.rs b/common/nymsphinx/params/src/packet_modes.rs new file mode 100644 index 00000000000..1f54d9e8519 --- /dev/null +++ b/common/nymsphinx/params/src/packet_modes.rs @@ -0,0 +1,58 @@ +// 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 std::convert::TryFrom; + +#[derive(Debug)] +pub struct InvalidPacketMode; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PacketMode { + /// Represents 'normal' packet sent through the network that should be delayed by an appropriate + /// value at each hop. + Mix = 0, + + /// Represents a VPN packet that should not be delayed and ideally cached pre-computed keys + /// should be used for unwrapping data. Note that it does not offer the same level of anonymity. + VPN = 1, +} + +impl PacketMode { + pub fn is_mix(self) -> bool { + self == PacketMode::Mix + } + + pub fn is_vpn(self) -> bool { + self == PacketMode::VPN + } +} + +impl TryFrom for PacketMode { + type Error = InvalidPacketMode; + + fn try_from(value: u8) -> std::result::Result { + match value { + _ if value == (PacketMode::Mix as u8) => Ok(Self::Mix), + _ if value == (PacketMode::VPN as u8) => Ok(Self::VPN), + _ => Err(InvalidPacketMode), + } + } +} + +impl Default for PacketMode { + fn default() -> Self { + PacketMode::Mix + } +} diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index 89ecb46e639..f74a4378b07 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -28,10 +28,11 @@ const ACK_IV_SIZE: usize = 16; const ACK_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + ACK_IV_SIZE + FRAG_ID_LEN; const EXTENDED_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024; +#[derive(Debug)] pub struct InvalidPacketSize; #[repr(u8)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum PacketSize { RegularPacket = 1, // for example instant messaging use case diff --git a/common/nymsphinx/src/lib.rs b/common/nymsphinx/src/lib.rs index 3d135581039..89300a7ca1c 100644 --- a/common/nymsphinx/src/lib.rs +++ b/common/nymsphinx/src/lib.rs @@ -22,6 +22,7 @@ pub use nymsphinx_addressing as addressing; pub use nymsphinx_anonymous_replies as anonymous_replies; pub use nymsphinx_chunking as chunking; pub use nymsphinx_cover as cover; +pub use nymsphinx_forwarding as forwarding; #[cfg(not(target_arch = "wasm32"))] pub use nymsphinx_framing as framing; pub use nymsphinx_params as params; diff --git a/common/nymsphinx/src/preparer.rs b/common/nymsphinx/src/preparer/mod.rs similarity index 82% rename from common/nymsphinx/src/preparer.rs rename to common/nymsphinx/src/preparer/mod.rs index 7851a7e0b48..975d84ffc23 100644 --- a/common/nymsphinx/src/preparer.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use self::vpn_manager::VPNManager; use crate::chunking; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; @@ -24,29 +25,31 @@ use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADD use nymsphinx_anonymous_replies::encryption_key::SURBEncryptionKey; use nymsphinx_anonymous_replies::reply_surb::ReplySURB; use nymsphinx_chunking::fragment::{Fragment, FragmentIdentifier}; +use nymsphinx_forwarding::packet::MixPacket; use nymsphinx_params::packet_sizes::PacketSize; use nymsphinx_params::{ - PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySURBEncryptionAlgorithm, + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, ReplySURBEncryptionAlgorithm, ReplySURBKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS, }; use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Delay, SphinxPacket}; +use nymsphinx_types::{delays, Delay}; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::time::Duration; use topology::{NymTopology, NymTopologyError}; +mod vpn_manager; + /// Represents fully packed and prepared [`Fragment`] that can be sent through the mix network. pub struct PreparedFragment { /// Indicates the total expected round-trip time, i.e. delay from the sending of this message /// until receiving the acknowledgement included inside of it. pub total_delay: Delay, - /// Indicates address of the node to which the message should be sent. - pub first_hop_address: NymNodeRoutingAddress, - - /// The actual 'chunk' of the message that is going to go through the mix network. - pub sphinx_packet: SphinxPacket, + /// Indicates all data required to serialize and forward the data. It contains the actual + /// address of the node to which the message should be sent, the actual 'chunk' of the message + /// going through the mix network and also the 'mode' of the packet, i.e. VPN or Mix. + pub mix_packet: MixPacket, } #[derive(Debug)] @@ -64,7 +67,7 @@ impl From for PreparationError { /// Prepares the message that is to be sent through the mix network by attaching /// an optional reply-SURB, padding it to appropriate length, encrypting its content, /// and chunking into appropriate size [`Fragment`]s. -#[derive(Debug, Clone)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] pub struct MessagePreparer { /// Instance of a cryptographically secure random number generator. rng: R, @@ -85,6 +88,14 @@ pub struct MessagePreparer { /// Number of mix hops each packet ('real' message, ack, reply) is expected to take. /// Note that it does not include gateway hops. num_mix_hops: u8, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + mode: PacketMode, + + /// If the VPN mode is activated, this underlying secret will be used for multiple sphinx + /// packets created. + vpn_manager: Option, } impl MessagePreparer @@ -92,11 +103,22 @@ where R: CryptoRng + Rng, { pub fn new( - rng: R, + mut rng: R, sender_address: Recipient, average_packet_delay: Duration, average_ack_delay: Duration, + mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { + let vpn_manager = if mode.is_vpn() { + Some(VPNManager::new( + &mut rng, + vpn_key_reuse_limit.expect("No key reuse limit provided in vpn mode!"), + )) + } else { + None + }; + MessagePreparer { rng, packet_size: Default::default(), @@ -104,6 +126,8 @@ where average_packet_delay, average_ack_delay, num_mix_hops: DEFAULT_NUM_MIX_HOPS, + mode, + vpn_manager, } } @@ -218,7 +242,7 @@ where /// - compute vk_b = g^x || v_b /// - compute sphinx_plaintext = SURB_ACK || g^x || v_b /// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext) - pub fn prepare_chunk_for_sending( + pub async fn prepare_chunk_for_sending( &mut self, fragment: Fragment, topology: &NymTopology, @@ -227,9 +251,20 @@ where ) -> Result { // create an ack let (ack_delay, surb_ack_bytes) = self - .generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)? + .generate_surb_ack(fragment.fragment_identifier(), topology, ack_key) + .await? .prepare_for_sending(); + // TODO: + // TODO: + // TODO: + // TODO: + // TODO: ASK @AP AND @DH WHETHER THOSE KEYS CAN/SHOULD ALSO BE REUSED IN VPN MODE!! + // TODO: + // TODO: + // TODO: + // TODO: + // create keys for 'payload' encryption let (ephemeral_keypair, shared_key) = new_ephemeral_shared_key::( @@ -269,11 +304,20 @@ where // create the actual sphinx packet here. With valid route and correct payload size, // there's absolutely no reason for this call to fail. - // note: once merged, that's an easy rng injection point for sphinx packets : ) - let sphinx_packet = SphinxPacketBuilder::new() - .with_payload_size(self.packet_size.payload_size()) - .build_packet(packet_payload, &route, &destination, &delays) - .unwrap(); + let sphinx_packet = if let Some(vpn_manager) = self.vpn_manager.as_mut() { + let initial_secret = vpn_manager.use_secret(&mut self.rng).await; + + SphinxPacketBuilder::new() + .with_payload_size(self.packet_size.payload_size()) + .with_initial_secret(&initial_secret) + .build_packet(packet_payload, &route, &destination, &delays) + .unwrap() + } else { + SphinxPacketBuilder::new() + .with_payload_size(self.packet_size.payload_size()) + .build_packet(packet_payload, &route, &destination, &delays) + .unwrap() + }; // from the previously constructed route extract the first hop let first_hop_address = @@ -283,26 +327,39 @@ where // the round-trip delay is the sum of delays of all hops on the forward route as // well as the total delay of the ack packet. total_delay: delays.iter().sum::() + ack_delay, - first_hop_address, - sphinx_packet, + mix_packet: MixPacket::new(first_hop_address, sphinx_packet, self.mode), }) } /// Construct an acknowledgement SURB for the given [`FragmentIdentifier`] - fn generate_surb_ack( + async fn generate_surb_ack( &mut self, fragment_id: FragmentIdentifier, topology: &NymTopology, ack_key: &AckKey, ) -> Result { - SURBAck::construct( - &mut self.rng, - &self.sender_address, - ack_key, - fragment_id.to_bytes(), - self.average_ack_delay, - topology, - ) + if let Some(vpn_manager) = self.vpn_manager.as_mut() { + let initial_secret = vpn_manager.use_secret(&mut self.rng).await; + SURBAck::construct( + &mut self.rng, + &self.sender_address, + ack_key, + fragment_id.to_bytes(), + self.average_ack_delay, + topology, + Some(&initial_secret), + ) + } else { + SURBAck::construct( + &mut self.rng, + &self.sender_address, + ack_key, + fragment_id.to_bytes(), + self.average_ack_delay, + topology, + None, + ) + } } /// Attaches an optional reply-surb and correct padding to the underlying message @@ -323,13 +380,13 @@ where } // TODO: perhaps the return type could somehow be combined with [`PreparedFragment`] ? - pub fn prepare_reply_for_use( + pub async fn prepare_reply_for_use( &mut self, message: Vec, reply_surb: ReplySURB, topology: &NymTopology, ack_key: &AckKey, - ) -> Result<(FragmentIdentifier, SphinxPacket, NymNodeRoutingAddress), PreparationError> { + ) -> Result<(MixPacket, FragmentIdentifier), PreparationError> { // there's no chunking in reply-surbs so there's a hard limit on message, // we also need to put the key digest into the message (same size as ephemeral key) // and need 1 byte to indicate padding length (this is not the case for 'normal' messages @@ -352,7 +409,8 @@ where // gateways could not distinguish reply packets from normal messages due to lack of said acks // note: the ack delay is irrelevant since we do not know the delay of actual surb let (_, surb_ack_bytes) = self - .generate_surb_ack(reply_id, topology, ack_key)? + .generate_surb_ack(reply_id, topology, ack_key) + .await? .prepare_for_sending(); let zero_pad_len = self.packet_size.plaintext_size() @@ -398,7 +456,7 @@ where .apply_surb(&packet_payload, Some(self.packet_size)) .unwrap(); - Ok((reply_id, packet, first_hop)) + Ok((MixPacket::new(first_hop, packet, self.mode), reply_id)) } #[allow(dead_code)] @@ -414,6 +472,8 @@ where average_packet_delay: Default::default(), average_ack_delay: Default::default(), num_mix_hops: DEFAULT_NUM_MIX_HOPS, + mode: Default::default(), + vpn_manager: None, } } } diff --git a/common/nymsphinx/src/preparer/vpn_manager.rs b/common/nymsphinx/src/preparer/vpn_manager.rs new file mode 100644 index 00000000000..7d4c0857fcf --- /dev/null +++ b/common/nymsphinx/src/preparer/vpn_manager.rs @@ -0,0 +1,154 @@ +// 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_types::EphemeralSecret; +use rand::{CryptoRng, Rng}; +use std::sync::atomic::{AtomicUsize, Ordering}; +#[cfg(not(target_arch = "wasm32"))] +use std::sync::Arc; +#[cfg(not(target_arch = "wasm32"))] +use tokio::sync::{RwLock, RwLockReadGuard}; + +#[cfg(not(target_arch = "wasm32"))] +pub(super) type SpinhxKeyRef<'a> = RwLockReadGuard<'a, EphemeralSecret>; + +#[cfg(target_arch = "wasm32")] +pub(super) type SpinhxKeyRef<'a> = &'a EphemeralSecret; + +#[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] +pub(super) struct VPNManager { + #[cfg(not(target_arch = "wasm32"))] + inner: Arc, + + #[cfg(target_arch = "wasm32")] + inner: Inner, +} + +struct Inner { + /// Maximum number of times particular sphinx-secret can be re-used before being rotated. + secret_reuse_limit: usize, + + /// Currently used initial sphinx-secret for the packets sent. + #[cfg(not(target_arch = "wasm32"))] + current_initial_secret: RwLock, + + #[cfg(target_arch = "wasm32")] + // this is a temporary work-around for wasm (which currently does not have retransmission + // and hence will not require multi-thread access) and also we can't import tokio's RWLock + // in wasm. + current_initial_secret: EphemeralSecret, + + /// If the client is running as VPN it's expected to keep re-using the same initial secret + /// for a while so that the mixnodes could cache some secret derivation results. However, + /// we should reset it every once in a while. + packets_with_current_secret: AtomicUsize, +} + +impl VPNManager { + #[cfg(not(target_arch = "wasm32"))] + pub(super) fn new(mut rng: R, secret_reuse_limit: usize) -> Self + where + R: CryptoRng + Rng, + { + let initial_secret = EphemeralSecret::new_with_rng(&mut rng); + VPNManager { + inner: Arc::new(Inner { + secret_reuse_limit, + current_initial_secret: RwLock::new(initial_secret), + packets_with_current_secret: AtomicUsize::new(0), + }), + } + } + + #[cfg(target_arch = "wasm32")] + pub(super) fn new(mut rng: R, secret_reuse_limit: usize) -> Self + where + R: CryptoRng + Rng, + { + let initial_secret = EphemeralSecret::new_with_rng(&mut rng); + VPNManager { + inner: Inner { + secret_reuse_limit, + current_initial_secret: initial_secret, + packets_with_current_secret: AtomicUsize::new(0), + }, + } + } + + #[cfg(not(target_arch = "wasm32"))] + pub(super) async fn rotate_secret(&mut self, mut rng: R) + where + R: CryptoRng + Rng, + { + let new_secret = EphemeralSecret::new_with_rng(&mut rng); + let mut write_guard = self.inner.current_initial_secret.write().await; + + *write_guard = new_secret; + // in here we have an exclusive lock so we don't have to have restrictive ordering as no + // other thread will be able to get here + self.inner + .packets_with_current_secret + .store(0, Ordering::Relaxed) + } + + // this method is async for consistency with non-wasm version + #[cfg(target_arch = "wasm32")] + pub(super) async fn rotate_secret(&mut self, mut rng: R) + where + R: CryptoRng + Rng, + { + let new_secret = EphemeralSecret::new_with_rng(&mut rng); + self.inner.current_initial_secret = new_secret; + + // wasm is single-threaded so relaxed ordering is also fine here + self.inner + .packets_with_current_secret + .store(0, Ordering::Relaxed); + } + + #[cfg(not(target_arch = "wasm32"))] + pub(super) async fn current_secret<'a>(&'a self) -> SpinhxKeyRef<'a> { + self.inner.current_initial_secret.read().await + } + + #[cfg(target_arch = "wasm32")] + pub(super) async fn current_secret<'a>(&'a self) -> SpinhxKeyRef<'a> { + &self.inner.current_initial_secret + } + + fn increment_key_usage(&mut self) { + // TODO: is this the appropriate ordering? + self.inner + .packets_with_current_secret + .fetch_add(1, Ordering::SeqCst); + } + + fn current_key_usage(&self) -> usize { + // TODO: is this the appropriate ordering? + self.inner + .packets_with_current_secret + .load(Ordering::SeqCst) + } + + pub(super) async fn use_secret<'a, R>(&'a mut self, rng: R) -> SpinhxKeyRef<'a> + where + R: CryptoRng + Rng, + { + if self.current_key_usage() > self.inner.secret_reuse_limit { + self.rotate_secret(rng).await; + } + self.increment_key_usage(); + self.current_secret().await + } +} diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 5fe2e758a89..ac00a39fa5b 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -7,5 +7,5 @@ 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="18aa34e1a39a5f3f14ba493ded9209658ff2cbfa" } +sphinx = { git = "https://github.com/nymtech/sphinx", rev="283dcc77dec8ee9ed3bed58c2b878e9c18320723" } #sphinx = { path = "../../../../sphinx"} \ No newline at end of file diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 62662050911..be6e59c7086 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -10,16 +10,18 @@ edition = "2018" [dependencies] clap = "2.33.0" dirs = "2.0.2" +# using 4.0.0 release candidate as it's faster than 3.X and more importantly it resolves edge cases deadlocks +dashmap = "4.0.0-rc6" dotenv = "0.15.0" futures = "0.3" log = "0.4" pretty_env_logger = "0.3" -rand = "0.7.2" +rand = "0.7" serde = { version = "1.0.104", features = ["derive"] } sled = "0.31" tokio = { version = "0.2", features = ["full"] } tokio-util = { version = "0.3.1", features = ["codec"] } -tokio-tungstenite = "0.11.0" +tokio-tungstenite = "0.11" # internal config = { path = "../common/config" } @@ -27,11 +29,12 @@ crypto = { path = "../common/crypto" } directory-client = { path = "../common/client-libs/directory-client" } gateway-requests = { path = "gateway-requests" } mixnet-client = { path = "../common/client-libs/mixnet-client" } +mixnode-common = { path = "../common/mixnode-common" } nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore" } [dependencies.tungstenite] -version = "0.10.0" +version = "0.11" default-features = false [build-dependencies] diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 31e78d4f2ca..289e1a58628 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -19,10 +19,11 @@ use crate::GatewayMacSize; use crypto::generic_array::typenum::Unsigned; use crypto::hmac::recompute_keyed_hmac_and_verify_tag; use crypto::symmetric::stream_cipher; -use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nymsphinx::addressing::nodes::NymNodeRoutingAddressError; +use nymsphinx::forwarding::packet::{MixPacket, MixPacketFormattingError}; use nymsphinx::params::packet_sizes::PacketSize; use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; -use nymsphinx::{DestinationAddressBytes, SphinxPacket}; +use nymsphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, @@ -73,10 +74,10 @@ pub enum GatewayRequestsError { RequestOfInvalidSize(usize), MalformedSphinxPacket, MalformedEncryption, + InvalidPacketMode, + InvalidMixPacket(MixPacketFormattingError), } -// to use it as `std::error::Error`, and we don't want to just derive is because we want -// the message to convey meanings of the usize tuple in RequestOfInvalidSize. impl fmt::Display for GatewayRequestsError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { use GatewayRequestsError::*; @@ -92,6 +93,8 @@ impl fmt::Display for GatewayRequestsError { ), MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), MalformedEncryption => write!(f, "the received encrypted data was malformed"), + InvalidPacketMode => write!(f, "provided packet mode is invalid"), + InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {}", err) } } } @@ -102,6 +105,12 @@ impl From for GatewayRequestsError { } } +impl From for GatewayRequestsError { + fn from(err: MixPacketFormattingError) -> Self { + GatewayRequestsError::InvalidMixPacket(err) + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub enum ClientControlRequest { @@ -205,10 +214,7 @@ impl TryFrom for ServerResponse { } pub enum BinaryRequest { - ForwardSphinx { - address: NymNodeRoutingAddress, - sphinx_packet: SphinxPacket, - }, + ForwardSphinx(MixPacket), } // Right now the only valid `BinaryRequest` is a request to forward a sphinx packet. @@ -249,40 +255,15 @@ impl BinaryRequest { ); // right now there's only a single option possible which significantly simplifies the logic - // if we decided to allow for more 'binary' messages, the API wouldn't need to change - let address = NymNodeRoutingAddress::try_from_bytes(&message_bytes_mut)?; - let addr_offset = address.bytes_min_len(); - - let sphinx_packet_data = &message_bytes_mut[addr_offset..]; - let packet_size = sphinx_packet_data.len(); - if PacketSize::get_type(packet_size).is_err() { - // TODO: should this allow AckPacket sizes? - - Err(GatewayRequestsError::RequestOfInvalidSize(packet_size)) - } else { - let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) { - Ok(packet) => packet, - Err(_) => return Err(GatewayRequestsError::MalformedSphinxPacket), - }; - - Ok(BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - }) - } + // if we decided to allow for more 'binary' messages, the API wouldn't need to change. + let mix_packet = MixPacket::try_from_bytes(message_bytes_mut)?; + Ok(BinaryRequest::ForwardSphinx(mix_packet)) } pub fn into_encrypted_tagged_bytes(self, shared_key: &SharedKeys) -> Vec { match self { - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } => { - let forwarding_data: Vec<_> = address - .as_bytes() - .into_iter() - .chain(sphinx_packet.to_bytes().into_iter()) - .collect(); + BinaryRequest::ForwardSphinx(mix_packet) => { + let forwarding_data = mix_packet.into_bytes(); // TODO: it could be theoretically slightly more efficient if the data wasn't taken // by reference because then it makes a copy for encryption rather than do it in place @@ -292,14 +273,8 @@ impl BinaryRequest { } // TODO: this will be encrypted, etc. - pub fn new_forward_request( - address: NymNodeRoutingAddress, - sphinx_packet: SphinxPacket, - ) -> BinaryRequest { - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } + pub fn new_forward_request(mix_packet: MixPacket) -> BinaryRequest { + BinaryRequest::ForwardSphinx(mix_packet) } pub fn into_ws_message(self, shared_key: &SharedKeys) -> Message { diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index adab45a2146..9a7157ebe0b 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -35,6 +35,7 @@ const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 10_000; // 10s const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s +const DEFAULT_CACHE_ENTRY_TTL: u64 = 30_000; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; @@ -397,6 +398,10 @@ impl Config { pub fn get_stored_messages_filename_length(&self) -> u16 { self.debug.stored_messages_filename_length } + + pub fn get_cache_entry_ttl(&self) -> time::Duration { + time::Duration::from_millis(self.debug.cache_entry_ttl) + } } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -578,6 +583,10 @@ pub struct Debug { /// if there are no real messages, dummy ones are create to always return /// `message_retrieval_limit` total messages message_retrieval_limit: u16, + + /// Duration for which a cached vpn processing result is going to get stored for. + /// The provided value is interpreted as milliseconds. + cache_entry_ttl: u64, } impl Default for Debug { @@ -589,6 +598,7 @@ impl Default for Debug { presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + cache_entry_ttl: DEFAULT_CACHE_ENTRY_TTL, } } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 8adde3292a4..8acc51aedd7 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -18,7 +18,6 @@ use crate::node::client_handling::clients_handler::{ use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, }; -use crate::node::mixnet_handling::sender::OutboundMixMessageSender; use crypto::asymmetric::identity; use futures::{ channel::{mpsc, oneshot}, @@ -31,6 +30,7 @@ use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys, D use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; use gateway_requests::BinaryResponse; use log::*; +use mixnet_client::forwarder::MixForwardingSender; use nymsphinx::DestinationAddressBytes; use std::convert::TryFrom; use std::sync::Arc; @@ -65,7 +65,7 @@ pub(crate) struct Handle { remote_address: Option, shared_key: Option, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, local_identity: Arc, @@ -77,7 +77,7 @@ impl Handle { pub(crate) fn new( conn: S, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, local_identity: Arc, ) -> Self { Handle { @@ -212,14 +212,8 @@ impl Handle { Err(e) => ServerResponse::new_error(e.to_string()), Ok(request) => match request { // currently only a single type exists - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } => { - // we know data has correct size (but nothing else besides of it) - self.outbound_mix_sender - .unbounded_send((address, sphinx_packet)) - .unwrap(); + BinaryRequest::ForwardSphinx(mix_packet) => { + self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); ServerResponse::Send { status: true } } }, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index e3acdbd9b4d..2c353c0c828 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -14,9 +14,9 @@ use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::node::client_handling::websocket::connection_handler::Handle; -use crate::node::mixnet_handling::sender::OutboundMixMessageSender; use crypto::asymmetric::identity; use log::*; +use mixnet_client::forwarder::MixForwardingSender; use std::net::SocketAddr; use std::sync::Arc; use tokio::task::JoinHandle; @@ -37,7 +37,7 @@ impl Listener { pub(crate) async fn run( &mut self, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, ) { info!("Starting websocket listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) @@ -66,7 +66,7 @@ impl Listener { pub(crate) fn start( mut self, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, ) -> JoinHandle<()> { tokio::spawn(async move { self.run(clients_handler_sender, outbound_mix_sender).await }) } diff --git a/gateway/src/node/mixnet_handling/mod.rs b/gateway/src/node/mixnet_handling/mod.rs index c4d03e42fad..b6644453927 100644 --- a/gateway/src/node/mixnet_handling/mod.rs +++ b/gateway/src/node/mixnet_handling/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. pub(crate) mod receiver; -pub(crate) mod sender; pub(crate) use receiver::listener::Listener; pub(crate) use receiver::packet_processing::PacketProcessor; diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 3389e774adb..66235786c5a 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -12,64 +12,241 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::node::client_handling::clients_handler::{ + ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, +}; +use crate::node::client_handling::websocket::message_receiver::MixMessageSender; use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::node::storage::inboxes::{ClientStorage, StoreData}; +use dashmap::DashMap; +use futures::channel::oneshot; use log::*; -use nymsphinx::framing::SphinxCodec; -use nymsphinx::SphinxPacket; +use mixnet_client::forwarder::MixForwardingSender; +use mixnode_common::cached_packet_processor::processor::ProcessedFinalHop; +use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::framing::codec::SphinxCodec; +use nymsphinx::framing::packet::FramedSphinxPacket; +use nymsphinx::DestinationAddressBytes; use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpStream; use tokio::prelude::*; use tokio::stream::StreamExt; use tokio_util::codec::Framed; -pub(crate) struct Handle { - peer_address: SocketAddr, - framed_connection: Framed, +pub(crate) struct ConnectionHandler { packet_processor: PacketProcessor, + + // TODO: method for cache invalidation so that we wouldn't keep all stale channel references + // we could use our friend DelayQueue. Alternatively we could periodically check for if the + // channels are closed. + available_socket_senders_cache: DashMap, + client_store: ClientStorage, + clients_handler_sender: ClientsHandlerRequestSender, + ack_sender: MixForwardingSender, } -impl Handle -where - S: AsyncRead + AsyncWrite + Unpin + 'static, -{ - // for time being we assume handle is always constructed from raw socket. - // if we decide we want to change it, that's not too difficult +impl ConnectionHandler { pub(crate) fn new( - peer_address: SocketAddr, - conn: S, packet_processor: PacketProcessor, + clients_handler_sender: ClientsHandlerRequestSender, + client_store: ClientStorage, + + ack_sender: MixForwardingSender, ) -> Self { - // we expect only to receive sphinx packets on this socket, so let's frame it here - let framed = Framed::new(conn, SphinxCodec); - Handle { - peer_address, - framed_connection: framed, + ConnectionHandler { packet_processor, + available_socket_senders_cache: DashMap::new(), + client_store, + clients_handler_sender, + ack_sender, + } + } + + pub(crate) fn clone_without_cache(&self) -> Self { + // TODO: should this be even cloned? + let senders_cache = DashMap::with_capacity(self.available_socket_senders_cache.capacity()); + for element_guard in self.available_socket_senders_cache.iter() { + let (k, v) = element_guard.pair(); + // TODO: this will be made redundant once there's some cache invalidator mechanism here + if !v.is_closed() { + senders_cache.insert(k.clone(), v.clone()); + } + } + + ConnectionHandler { + packet_processor: self.packet_processor.clone_without_key_cache(), + available_socket_senders_cache: senders_cache, + client_store: self.client_store.clone(), + clients_handler_sender: self.clients_handler_sender.clone(), + ack_sender: self.ack_sender.clone(), + } + } + + fn try_push_message_to_client( + &self, + sender_channel: Option, + message: Vec, + ) -> Result<(), Vec> { + match sender_channel { + 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()) + } + } + } + + fn remove_stale_client_sender(&self, client_address: &DestinationAddressBytes) { + if !self.available_socket_senders_cache.remove(client_address) { + warn!( + "Tried to remove stale entry for non-existent client sender: {}", + client_address + ) + } + } + + async fn try_to_obtain_client_ws_message_sender( + &self, + client_address: DestinationAddressBytes, + ) -> Option { + let mut should_remove_stale = false; + if let Some(sender_ref) = self.available_socket_senders_cache.get(&client_address) { + let sender = sender_ref.value(); + if !sender.is_closed() { + return Some(sender.clone()); + } else { + should_remove_stale = true; + } + } + + // we want to do it outside the immutable borrow into the map + if should_remove_stale { + self.remove_stale_client_sender(&client_address) } + + // if we got here it means that either we have no sender channel for this client or it's closed + // so we must refresh it from the source, i.e. ClientsHandler + let (res_sender, res_receiver) = oneshot::channel(); + let clients_handler_request = + ClientsHandlerRequest::IsOnline(client_address.clone(), res_sender); + self.clients_handler_sender + .unbounded_send(clients_handler_request) + .unwrap(); // the receiver MUST BE alive + + let client_sender = match res_receiver.await.unwrap() { + ClientsHandlerResponse::IsOnline(client_sender) => client_sender, + _ => panic!("received response to wrong query!"), // again, this should NEVER happen + }?; + + // finally update the cache + if self + .available_socket_senders_cache + .insert(client_address, client_sender.clone()) + { + // this warning is harmless, but I want to see if it's realistically for it to even occur + warn!("Other thread already updated cache for client sender!") + } + + Some(client_sender) } - async fn process_received_packet( - sphinx_packet: SphinxPacket, - mut packet_processor: PacketProcessor, - ) { - match packet_processor.process_sphinx_packet(sphinx_packet).await { - Ok(_) => trace!("successfully processed [and forwarded/stored] a final hop packet"), - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + pub(crate) async fn store_processed_packet_payload( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> io::Result<()> { + debug!( + "Storing received message for {} on the disk...", + client_address + ); + + let store_data = StoreData::new(client_address, message); + self.client_store.store_processed_data(store_data).await + } + + fn forward_ack(&self, forward_ack: Option, client_address: DestinationAddressBytes) { + if let Some(forward_ack) = forward_ack { + trace!( + "Sending ack from packet for {} to {}", + client_address, + forward_ack.next_hop() + ); + + self.ack_sender.unbounded_send(forward_ack).unwrap(); } } - pub(crate) async fn start_handling(&mut self) { - 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 - // scheduler) - tokio::spawn(Self::process_received_packet( - sphinx_packet, - self.packet_processor.clone(), - )); + async fn handle_processed_packet(&self, processed_final_hop: ProcessedFinalHop) { + let client_address = processed_final_hop.destination; + let message = processed_final_hop.message; + let forward_ack = processed_final_hop.forward_ack; + + let client_sender = self + .try_to_obtain_client_ws_message_sender(client_address) + .await; + + // we failed to push message directly to the client - it's probably offline. + // we should store it on the disk instead. + match self.try_push_message_to_client(client_sender, message) { + Err(unsent_plaintext) => match self + .store_processed_packet_payload(client_address, unsent_plaintext) + .await + { + Err(err) => error!("Failed to store client data - {}", err), + Ok(_) => trace!("Stored packet for {}", client_address), + }, + Ok(_) => trace!("Pushed received packet to {}", client_address), + } + + // if we managed to either push message directly to the [online] client or store it at + // its inbox, it means that it must exist at this gateway, hence we can send the + // received ack back into the network + self.forward_ack(forward_ack, client_address); + } + + async fn handle_received_packet(self: Arc, framed_sphinx_packet: FramedSphinxPacket) { + // + // TODO: here be replay attack detection - it will require similar key cache to the one in + // packet processor for vpn packets, + // question: can it also be per connection vs global? + // + + let processed_final_hop = match self + .packet_processor + .process_received(framed_sphinx_packet) + .await + { + Err(e) => { + debug!("We failed to process received sphinx packet - {:?}", e); + return; + } + Ok(processed_final_hop) => processed_final_hop, + }; + + self.handle_processed_packet(processed_final_hop).await + } + + pub(crate) async fn handle_connection(self, conn: TcpStream, remote: SocketAddr) { + debug!("Starting connection handler for {:?}", remote); + let this = Arc::new(self); + let mut framed_conn = Framed::new(conn, SphinxCodec); + while let Some(framed_sphinx_packet) = framed_conn.next().await { + match framed_sphinx_packet { + Ok(framed_sphinx_packet) => { + // TODO: benchmark spawning tokio task with full processing vs just processing it + // synchronously (without delaying inside of course, + // delay could be moved to a per-connection DelayQueue. The delay queue future + // could automatically just forward packet that is done being delayed) + // under higher load in single and multi-threaded situation. + // + // My gut feeling is saying that we might get some nice performance boost + // if we introduced the change + let this = Arc::clone(&this); + tokio::spawn(this.handle_received_packet(framed_sphinx_packet)); } Err(err) => { error!( @@ -80,6 +257,10 @@ where } } } - info!("Closing connection from {:?}", self.peer_address); + + info!( + "Closing connection from {:?}", + framed_conn.into_inner().peer_addr() + ); } } diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 9532a37af5a..36002e6fca0 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::node::mixnet_handling::receiver::{ - connection_handler::Handle, packet_processing::PacketProcessor, -}; +use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use log::*; use std::net::SocketAddr; use tokio::task::JoinHandle; @@ -23,12 +21,13 @@ pub(crate) struct Listener { address: SocketAddr, } +// TODO: this file is nearly identical to the one in mixnode impl Listener { pub(crate) fn new(address: SocketAddr) -> Self { Listener { address } } - pub(crate) async fn run(&mut self, packet_processor: PacketProcessor) { + pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) { info!("Starting mixnet listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) .await @@ -37,16 +36,17 @@ impl Listener { loop { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { - trace!("received a socket connection from {}", remote_addr); - let mut handle = Handle::new(remote_addr, socket, packet_processor.clone()); - tokio::spawn(async move { handle.start_handling().await }); + let handler = connection_handler.clone_without_cache(); + tokio::spawn(handler.handle_connection(socket, remote_addr)); } Err(e) => warn!("failed to get client: {:?}", e), } } } - pub(crate) fn start(mut self, packet_processor: PacketProcessor) -> JoinHandle<()> { - tokio::spawn(async move { self.run(packet_processor).await }) + pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { + info!("Running mix listener on {:?}", self.address.to_string()); + + tokio::spawn(async move { self.run(connection_handler).await }) } } diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index bc87c08e72f..3cb19bce213 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -12,285 +12,56 @@ // See the License for the specific language governing permissions and // limitations under the License. -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::asymmetric::encryption; -use futures::channel::oneshot; -use futures::lock::Mutex; -use log::*; -use nymsphinx::acknowledgements::surb_ack::{SURBAck, SURBAckRecoveryError}; -use nymsphinx::params::packet_sizes::PacketSize; -use nymsphinx::{DestinationAddressBytes, Error as SphinxError, ProcessedPacket, SphinxPacket}; - -use std::collections::HashMap; -use std::io; -use std::sync::Arc; +use mixnode_common::cached_packet_processor::error::MixProcessingError; +pub use mixnode_common::cached_packet_processor::processor::MixProcessingResult; +use mixnode_common::cached_packet_processor::processor::{ + CachedPacketProcessor, ProcessedFinalHop, +}; +use nymsphinx::framing::packet::FramedSphinxPacket; +use tokio::time::Duration; #[derive(Debug)] -pub enum MixProcessingError { - ReceivedForwardHopError, - UnsupportedSphinxPacketSize(usize), - SphinxProcessingError(SphinxError), - IncorrectlyFormattedSURBAck(SURBAckRecoveryError), - IOError(io::Error), -} - -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; - - SphinxProcessingError(err) - } +pub enum GatewayProcessingError { + PacketProcessingError(MixProcessingError), + ForwardHopReceivedError, } -impl From for MixProcessingError { - fn from(e: io::Error) -> Self { - use MixProcessingError::*; +impl From for GatewayProcessingError { + fn from(e: MixProcessingError) -> Self { + use GatewayProcessingError::*; - IOError(e) - } -} - -impl From for MixProcessingError { - fn from(err: SURBAckRecoveryError) -> Self { - use MixProcessingError::*; - - IncorrectlyFormattedSURBAck(err) + PacketProcessingError(e) } } // PacketProcessor contains all data required to correctly unwrap and store sphinx packets -#[derive(Clone)] pub struct PacketProcessor { - encryption_keys: Arc, - // TODO: later investigate some concurrent hashmap solutions or perhaps RWLocks. - // Right now Mutex is the simplest and fastest to implement approach - available_socket_senders_cache: Arc>>, - client_store: ClientStorage, - clients_handler_sender: ClientsHandlerRequestSender, - ack_sender: OutboundMixMessageSender, + inner_processor: CachedPacketProcessor, } impl PacketProcessor { - pub(crate) fn new( - encryption_keys: Arc, - clients_handler_sender: ClientsHandlerRequestSender, - client_store: ClientStorage, - ack_sender: OutboundMixMessageSender, - ) -> Self { + pub(crate) fn new(encryption_key: &encryption::PrivateKey, cache_entry_ttl: Duration) -> Self { PacketProcessor { - available_socket_senders_cache: Arc::new(Mutex::new(HashMap::new())), - clients_handler_sender, - client_store, - encryption_keys, - ack_sender, - } - } - - fn try_push_message_to_client( - &self, - sender_channel: Option, - message: Vec, - ) -> Result<(), Vec> { - match sender_channel { - 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()) - } + inner_processor: CachedPacketProcessor::new(encryption_key.into(), cache_entry_ttl), } } - async fn try_to_obtain_client_ws_message_sender( - &mut self, - client_address: DestinationAddressBytes, - ) -> Option { - let mut cache_guard = self.available_socket_senders_cache.lock().await; - - if let Some(sender) = cache_guard.get(&client_address) { - if !sender.is_closed() { - return Some(sender.clone()); - } else { - cache_guard.remove(&client_address); - } - } - - // do not block other readers to the cache while we are doing some blocking work here - drop(cache_guard); - - // if we got here it means that either we have no sender channel for this client or it's closed - // so we must refresh it from the source, i.e. ClientsHandler - let (res_sender, res_receiver) = oneshot::channel(); - let clients_handler_request = - ClientsHandlerRequest::IsOnline(client_address.clone(), res_sender); - self.clients_handler_sender - .unbounded_send(clients_handler_request) - .unwrap(); // the receiver MUST BE alive - - let client_sender = match res_receiver.await.unwrap() { - ClientsHandlerResponse::IsOnline(client_sender) => client_sender, - _ => panic!("received response to wrong query!"), // again, this should NEVER happen - }; - - client_sender.as_ref()?; - - let client_sender = client_sender.unwrap(); - // finally re-acquire the lock to update the cache - let mut cache_guard = self.available_socket_senders_cache.lock().await; - cache_guard.insert(client_address, client_sender.clone()); - - Some(client_sender) - } - - pub(crate) async fn store_processed_packet_payload( - &self, - client_address: DestinationAddressBytes, - message: Vec, - ) -> io::Result<()> { - debug!( - "Storing received packet for {:?} on the disk...", - client_address.to_base58_string() - ); - // we are temporarily ignoring and not storing obvious loop cover traffic messages to - // 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 nymsphinx::cover::is_cover(&message) { - debug!("Received a loop cover message - not going to store it"); - return Ok(()); + pub(crate) fn clone_without_key_cache(&self) -> Self { + PacketProcessor { + inner_processor: self.inner_processor.clone_without_cache(), } - - let store_data = StoreData::new(client_address, message); - self.client_store.store_processed_data(store_data).await } - pub(crate) fn unwrap_sphinx_packet( + pub(crate) async fn process_received( &self, - packet: SphinxPacket, - ) -> Result<(DestinationAddressBytes, Vec), MixProcessingError> { - match packet.process(&self.encryption_keys.as_ref().private_key().into()) { - Ok(ProcessedPacket::ProcessedPacketForwardHop(_, _, _)) => { - 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 message = payload.recover_plaintext()?; - Ok((client_address, message)) - } - Err(e) => { - warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError(e)) + received: FramedSphinxPacket, + ) -> Result { + match self.inner_processor.process_received(received).await? { + MixProcessingResult::ForwardHop(..) => { + Err(GatewayProcessingError::ForwardHopReceivedError) } + MixProcessingResult::FinalHop(processed_final) => Ok(processed_final), } } - - fn split_plaintext_into_ack_and_message( - &self, - mut extracted_plaintext: Vec, - ) -> (Vec, Vec) { - 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; - - 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.into()); - } 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() - ); - } - - // 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, ack_packet)) - .unwrap(); - } - - Ok(()) - } } diff --git a/gateway/src/node/mixnet_handling/sender/mod.rs b/gateway/src/node/mixnet_handling/sender/mod.rs deleted file mode 100644 index 5bed92ef734..00000000000 --- a/gateway/src/node/mixnet_handling/sender/mod.rs +++ /dev/null @@ -1,74 +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. - -// TODO: code is nearly identical to mixnode::node::packet_forwarding -> perhaps it should be put to common? - -use futures::channel::mpsc; -use futures::StreamExt; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::time::Duration; -use tokio::task::JoinHandle; - -pub(crate) type OutboundMixMessageSender = - mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>; -pub(crate) type OutboundMixMessageReceiver = - mpsc::UnboundedReceiver<(NymNodeRoutingAddress, SphinxPacket)>; - -pub(crate) struct PacketForwarder { - mixnet_client: mixnet_client::Client, - conn_tx: OutboundMixMessageSender, - conn_rx: OutboundMixMessageReceiver, -} - -impl PacketForwarder { - pub(crate) fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> PacketForwarder { - let tcp_client_config = mixnet_client::Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ); - - let (conn_tx, conn_rx) = mpsc::unbounded(); - - PacketForwarder { - mixnet_client: mixnet_client::Client::new(tcp_client_config), - conn_tx, - conn_rx, - } - } - - pub(crate) fn start(mut self) -> (JoinHandle<()>, OutboundMixMessageSender) { - let sender_channel = self.conn_tx.clone(); - ( - tokio::spawn(async move { - while let Some((address, packet)) = self.conn_rx.next().await { - trace!("Going to forward packet to {:?}", address); - // as a mix node we don't care about responses, we just want to fire packets - // as quickly as possible - self.mixnet_client - .send(address, packet, false) - .await - .unwrap(); - // if we're not waiting for response, we MUST get an Ok - } - }), - sender_channel, - ) - } -} diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index e0a2dc3fa55..c47420349cc 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -15,11 +15,12 @@ use crate::config::Config; use crate::node::client_handling::clients_handler::{ClientsHandler, ClientsHandlerRequestSender}; use crate::node::client_handling::websocket; -use crate::node::mixnet_handling::sender::{OutboundMixMessageSender, PacketForwarder}; +use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::storage::{inboxes, ClientLedger}; use crypto::asymmetric::{encryption, identity}; use directory_client::DirectoryClient; use log::*; +use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; use std::sync::Arc; use tokio::runtime::Runtime; @@ -65,24 +66,30 @@ impl Gateway { fn start_mix_socket_listener( &self, clients_handler_sender: ClientsHandlerRequestSender, - ack_sender: OutboundMixMessageSender, + ack_sender: MixForwardingSender, ) { info!("Starting mix socket listener..."); let packet_processor = mixnet_handling::PacketProcessor::new( - Arc::clone(&self.encryption_keys), + self.encryption_keys.private_key(), + self.config.get_cache_entry_ttl(), + ); + + let connection_handler = ConnectionHandler::new( + packet_processor, clients_handler_sender, self.client_inbox_storage.clone(), ack_sender, ); - mixnet_handling::Listener::new(self.config.get_mix_listening_address()) - .start(packet_processor); + let listener = mixnet_handling::Listener::new(self.config.get_mix_listening_address()); + + listener.start(connection_handler); } fn start_client_websocket_listener( &self, - forwarding_channel: OutboundMixMessageSender, + forwarding_channel: MixForwardingSender, clients_handler_sender: ClientsHandlerRequestSender, ) { info!("Starting client [web]socket listener..."); @@ -94,16 +101,17 @@ impl Gateway { .start(clients_handler_sender, forwarding_channel); } - fn start_packet_forwarder(&self) -> OutboundMixMessageSender { + fn start_packet_forwarder(&self) -> MixForwardingSender { info!("Starting mix packet forwarder..."); - let (_, forwarding_channel) = PacketForwarder::new( + let (mut packet_forwarder, packet_sender) = PacketForwarder::new( self.config.get_packet_forwarding_initial_backoff(), self.config.get_packet_forwarding_maximum_backoff(), self.config.get_initial_connection_timeout(), - ) - .start(); - forwarding_channel + ); + + tokio::spawn(async move { packet_forwarder.run().await }); + packet_sender } fn start_clients_handler(&self) -> ClientsHandlerRequestSender { @@ -170,7 +178,6 @@ impl Gateway { let mut runtime = Runtime::new().unwrap(); runtime.block_on(async { - if let Some(duplicate_gateway_key) = self.check_if_same_ip_gateway_exists().await { error!( "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", @@ -179,8 +186,6 @@ impl Gateway { return; } - - let mix_forwarding_channel = self.start_packet_forwarder(); let clients_handler_sender = self.start_clients_handler(); diff --git a/mixnode/Cargo.lock b/mixnode/Cargo.lock deleted file mode 100644 index 8e2c586d7a0..00000000000 --- a/mixnode/Cargo.lock +++ /dev/null @@ -1,2371 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "aes-ctr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ctr 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aes-soft" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aesni" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "arc-swap" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayref" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "blake2b_simd" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-cipher-trait" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "built" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bytes" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cc" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "chrono" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ctr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "curve25519-dalek" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dirs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dirs-sys" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "encoding_rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "flate2" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-channel" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-executor" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-io" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-macro" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-sink" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-task" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "getrandom" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "git2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hermit-abi" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hex" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hkdf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hyper" -version = "0.12.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hyper-tls" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "input_buffer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "jobserver" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libgit2-sys" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libz-sys" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lock_api" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memoffset" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mime" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "mime_guess" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-uds" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "native-tls" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "net2" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-integer" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num_cpus" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nym-client" -version = "0.2.0" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "sfw-provider-requests 0.1.0", - "sphinx 0.1.0", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tungstenite 0.10.0 (git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38)", - "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nym-mixnode" -version = "0.2.0" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nym-client 0.2.0", - "sphinx 0.1.0", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl" -version = "0.10.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl-sys" -version = "0.9.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pem" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-project" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pkg-config" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro-hack" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro-nested" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro2" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "publicsuffix" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_distr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_users" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "remove_dir_all" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "reqwest" -version = "0.9.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rust-argon2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "schannel" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "security-framework" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "security-framework-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sfw-provider-requests" -version = "0.1.0" -dependencies = [ - "sphinx 0.1.0", -] - -[[package]] -name = "sha-1" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sha2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "signal-hook-registry" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "smallvec" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "socket2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sphinx" -version = "0.1.0" -dependencies = [ - "aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lioness 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "stream-cipher" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "subtle" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "syn" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "synstructure" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tempfile" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-executor" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-timer" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.10.0" -source = "git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38#6dc2018cbfe8fe7ddd75ff977343086503135b38" -dependencies = [ - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "toml" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "try_from" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tungstenite" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "url" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf-8" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasi" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -"checksum aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" -"checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" -"checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" -"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" -"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" -"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -"checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" -"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" -"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -"checksum built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2315cfb416f86e05360edc950b1d7d25ecfb00f7f8eba60dbd7882a0f2e944" -"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -"checksum bytes 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c85319f157e4e26c703678e68e26ab71a46c0199286fa670b21cc9fec13d895" -"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" -"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" -"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" -"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" -"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" -"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" -"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -"checksum ctr 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736" -"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" -"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" -"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" -"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" -"checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" -"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" -"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" -"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" -"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" -"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" -"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" -"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -"checksum git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c1af51ea8a906616af45a4ce78eacf25860f7a13ae7bf8a814693f0f4037a26" -"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" -"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e" -"checksum hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" -"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)" = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" -"checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -"checksum input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" -"checksum libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4870c781f6063efb83150cd22c1ddf6ecf58531419e7570cdcced46970f64a16" -"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" -"checksum lioness 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" -"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" -"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" -"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" -"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" -"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" -"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" -"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" -"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" -"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" -"checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" -"checksum pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" -"checksum publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" -"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab" -"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" -"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" -"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" -"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" -"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" -"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" -"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" -"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" -"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" -"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" -"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" -"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" -"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" -"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" -"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -"checksum tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "beeef686ef92a222de07e089f455d9f8478bbba9651718f9e4b276babe829082" -"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -"checksum tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ca6df436c42b0c3330a82d855d2ef017cd793090ad550a6bc2184f4b933532ab" -"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -"checksum tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5795a71419535c6dcecc9b6ca95bdd3c2d6142f7e8343d7beb9923f129aa87e" -"checksum tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6732fe6b53c8d11178dcb77ac6d9682af27fc6d4cb87789449152e5377377146" -"checksum tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -"checksum tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c32ffea4827978e9aa392d2f743d973c1dfa3730a2ed3f22ce1e6984da848c" -"checksum tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1739638e364e558128461fc1ad84d997702c8e31c2e6b18fb99842268199e827" -"checksum tokio-tungstenite 0.10.0 (git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38)" = "" -"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" -"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -"checksum tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" -"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" -"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" -"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" -"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 3cc19b0b270..975e5a5080f 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -25,6 +25,7 @@ config = {path = "../common/config"} crypto = {path = "../common/crypto"} directory-client = { path = "../common/client-libs/directory-client" } mixnet-client = { path = "../common/client-libs/mixnet-client" } +mixnode-common = { path = "../common/mixnode-common" } nymsphinx = {path = "../common/nymsphinx" } pemstore = {path = "../common/pemstore"} topology = {path = "../common/topology"} diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index e98206bf1e0..ed9af6b40e2 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -36,6 +36,7 @@ const DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY: u64 = 60_000; // 1min const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s +const DEFAULT_CACHE_ENTRY_TTL: u64 = 30_000; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -257,6 +258,10 @@ impl Config { pub fn get_initial_connection_timeout(&self) -> time::Duration { time::Duration::from_millis(self.debug.initial_connection_timeout) } + + pub fn get_cache_entry_ttl(&self) -> time::Duration { + time::Duration::from_millis(self.debug.cache_entry_ttl) + } } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -375,6 +380,10 @@ pub struct Debug { /// Timeout for establishing initial connection when trying to forward a sphinx packet. /// The provider value is interpreted as milliseconds. initial_connection_timeout: u64, + + /// Duration for which a cached vpn processing result is going to get stored for. + /// The provided value is interpreted as milliseconds. + cache_entry_ttl: u64, } impl Default for Debug { @@ -386,6 +395,7 @@ impl Default for Debug { packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + cache_entry_ttl: DEFAULT_CACHE_ENTRY_TTL, } } } diff --git a/mixnode/src/node/listener.rs b/mixnode/src/node/listener.rs deleted file mode 100644 index 7fec60304ea..00000000000 --- a/mixnode/src/node/listener.rs +++ /dev/null @@ -1,112 +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::node::packet_processing::{MixProcessingResult, PacketProcessor}; -use futures::channel::mpsc; -use log::*; -use nymsphinx::framing::SphinxCodec; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::io; -use std::net::SocketAddr; -use tokio::runtime::Handle; -use tokio::stream::StreamExt; -use tokio::task::JoinHandle; -use tokio_util::codec::Framed; - -async fn process_received_packet( - sphinx_packet: SphinxPacket, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) { - // all processing incl. delay was done, the only thing left is to forward it - match packet_processor.process_sphinx_packet(sphinx_packet).await { - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), - Ok(res) => match res { - MixProcessingResult::ForwardHop(hop_address, forward_packet) => { - // send our data to tcp client for forwarding. If forwarding fails, then it fails, - // it's not like we can do anything about it - // - // in unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - forwarding_channel - .unbounded_send((hop_address, forward_packet)) - .unwrap(); - packet_processor.report_sent(hop_address); - } - MixProcessingResult::LoopMessage => { - warn!("Somehow processed a loop cover message that we haven't implemented yet!") - } - }, - } -} - -async fn process_socket_connection( - socket: tokio::net::TcpStream, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) { - let mut framed = Framed::new(socket, SphinxCodec); - while let Some(sphinx_packet) = framed.next().await { - match sphinx_packet { - Ok(sphinx_packet) => { - // 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 - // scheduler) - tokio::spawn(process_received_packet( - sphinx_packet, - packet_processor.clone(), - forwarding_channel.clone(), - )); - } - Err(err) => { - error!( - "The socket connection got corrupted with error: {:?}. Closing the socket", - err - ); - return; - } - } - } - info!( - "Closing connection from {:?}", - framed.into_inner().peer_addr() - ); -} - -pub(crate) fn run_socket_listener( - handle: &Handle, - addr: SocketAddr, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) -> JoinHandle> { - let handle_clone = handle.clone(); - handle.spawn(async move { - let mut listener = tokio::net::TcpListener::bind(addr).await?; - loop { - let (socket, _) = listener.accept().await?; - - let thread_packet_processor = packet_processor.clone(); - let forwarding_channel_clone = forwarding_channel.clone(); - handle_clone.spawn(async move { - process_socket_connection( - socket, - thread_packet_processor, - forwarding_channel_clone, - ) - .await; - }); - } - }) -} diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs new file mode 100644 index 00000000000..6f931f52bc2 --- /dev/null +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -0,0 +1,123 @@ +// 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::node::listener::connection_handler::packet_processing::{ + MixProcessingResult, PacketProcessor, +}; +use log::*; +use mixnet_client::forwarder::MixForwardingSender; +use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::framing::codec::SphinxCodec; +use nymsphinx::framing::packet::FramedSphinxPacket; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio::stream::StreamExt; +use tokio_util::codec::Framed; + +pub(crate) mod packet_processing; + +pub(crate) struct ConnectionHandler { + packet_processor: PacketProcessor, + forwarding_channel: MixForwardingSender, +} + +impl ConnectionHandler { + pub(crate) fn new( + packet_processor: PacketProcessor, + forwarding_channel: MixForwardingSender, + ) -> Self { + ConnectionHandler { + packet_processor, + forwarding_channel, + } + } + + pub(crate) fn clone_without_cache(&self) -> Self { + ConnectionHandler { + packet_processor: self.packet_processor.clone_without_cache(), + forwarding_channel: self.forwarding_channel.clone(), + } + } + + fn forward_packet(&self, mix_packet: MixPacket) { + let routing_address = mix_packet.next_hop(); + // send our data to tcp client for forwarding. If forwarding fails, then it fails, + // it's not like we can do anything about it + // + // in unbounded_send() failed it means that the receiver channel was disconnected + // and hence something weird must have happened without a way of recovering + self.forwarding_channel.unbounded_send(mix_packet).unwrap(); + self.packet_processor.report_sent(routing_address); + } + + async fn handle_received_packet(self: Arc, framed_sphinx_packet: FramedSphinxPacket) { + // + // TODO: here be replay attack detection - it will require similar key cache to the one in + // packet processor for vpn packets, + // question: can it also be per connection vs global? + // + + // all processing including delaying, key caching, etc. was done, the only thing left is to forward it + match self + .packet_processor + .process_received(framed_sphinx_packet) + .await + { + Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + Ok(res) => match res { + MixProcessingResult::ForwardHop(forward_packet) => { + self.forward_packet(forward_packet) + } + MixProcessingResult::FinalHop(..) => { + warn!("Somehow processed a loop cover message that we haven't implemented yet!") + } + }, + } + } + + pub(crate) async fn handle_connection(self, conn: TcpStream, remote: SocketAddr) { + debug!("Starting connection handler for {:?}", remote); + let this = Arc::new(self); + let mut framed_conn = Framed::new(conn, SphinxCodec); + while let Some(framed_sphinx_packet) = framed_conn.next().await { + match framed_sphinx_packet { + Ok(framed_sphinx_packet) => { + // TODO: benchmark spawning tokio task with full processing vs just processing it + // synchronously (without delaying inside of course, + // delay could be moved to a per-connection DelayQueue. The delay queue future + // could automatically just forward packet that is done being delayed) + // under higher load in single and multi-threaded situation. + // + // My gut feeling is saying that we might get some nice performance boost + // if we introduced the change + let this = Arc::clone(&this); + tokio::spawn(this.handle_received_packet(framed_sphinx_packet)); + } + Err(err) => { + error!( + "The socket connection got corrupted with error: {:?}. Closing the socket", + err + ); + return; + } + } + } + + info!( + "Closing connection from {:?}", + framed_conn.into_inner().peer_addr() + ); + } +} diff --git a/mixnode/src/node/listener/connection_handler/packet_processing.rs b/mixnode/src/node/listener/connection_handler/packet_processing.rs new file mode 100644 index 00000000000..ab006a3b35a --- /dev/null +++ b/mixnode/src/node/listener/connection_handler/packet_processing.rs @@ -0,0 +1,60 @@ +// 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::node::metrics; +use crypto::asymmetric::encryption; +use mixnode_common::cached_packet_processor::error::MixProcessingError; +use mixnode_common::cached_packet_processor::processor::CachedPacketProcessor; +pub use mixnode_common::cached_packet_processor::processor::MixProcessingResult; +use nymsphinx::addressing::nodes::NymNodeRoutingAddress; +use nymsphinx::framing::packet::FramedSphinxPacket; +use tokio::time::Duration; + +// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets +pub struct PacketProcessor { + inner_processor: CachedPacketProcessor, + metrics_reporter: metrics::MetricsReporter, +} + +impl PacketProcessor { + pub(crate) fn new( + encryption_key: &encryption::PrivateKey, + metrics_reporter: metrics::MetricsReporter, + cache_entry_ttl: Duration, + ) -> Self { + PacketProcessor { + inner_processor: CachedPacketProcessor::new(encryption_key.into(), cache_entry_ttl), + metrics_reporter, + } + } + + pub(crate) fn clone_without_cache(&self) -> Self { + PacketProcessor { + inner_processor: self.inner_processor.clone_without_cache(), + metrics_reporter: self.metrics_reporter.clone(), + } + } + + pub(crate) fn report_sent(&self, address: NymNodeRoutingAddress) { + self.metrics_reporter.report_sent(address.to_string()) + } + + pub(crate) async fn process_received( + &self, + received: FramedSphinxPacket, + ) -> Result { + self.metrics_reporter.report_received(); + self.inner_processor.process_received(received).await + } +} diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs new file mode 100644 index 00000000000..cc3ca62a554 --- /dev/null +++ b/mixnode/src/node/listener/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::node::listener::connection_handler::ConnectionHandler; +use log::*; +use std::net::SocketAddr; +use tokio::net::TcpListener; +use tokio::task::JoinHandle; + +pub(crate) mod connection_handler; + +pub(crate) struct Listener { + address: SocketAddr, +} + +impl Listener { + pub(crate) fn new(address: SocketAddr) -> Self { + Listener { address } + } + + async fn run(&mut self, connection_handler: ConnectionHandler) { + let mut listener = TcpListener::bind(self.address) + .await + .expect("Failed to create TCP listener"); + loop { + match listener.accept().await { + Ok((socket, remote_addr)) => { + let handler = connection_handler.clone_without_cache(); + tokio::spawn(handler.handle_connection(socket, remote_addr)); + } + Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + } + } + } + + pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { + info!("Running mix listener on {:?}", self.address.to_string()); + + tokio::spawn(async move { self.run(connection_handler).await }) + } +} diff --git a/mixnode/src/node/metrics.rs b/mixnode/src/node/metrics.rs index 1ce93157031..25936d0a89f 100644 --- a/mixnode/src/node/metrics.rs +++ b/mixnode/src/node/metrics.rs @@ -21,7 +21,6 @@ use log::*; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use tokio::runtime::Handle; use tokio::task::JoinHandle; type SentMetricsMap = HashMap; @@ -88,8 +87,8 @@ impl MetricsReceiver { } } - fn start(mut self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { + fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { while let Some(metrics_data) = self.metrics_rx.next().await { match metrics_data { MetricEvent::Received => self.metrics.increment_received_metrics().await, @@ -129,8 +128,8 @@ impl MetricsSender { } } - fn start(mut self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { + fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { loop { // set the deadline in the future let sending_delay = tokio::time::delay_for(self.sending_delay); @@ -283,10 +282,10 @@ impl MetricsController { } // reporter is how node is going to be accessing the metrics data - pub(crate) fn start(self, handle: &Handle) -> MetricsReporter { + pub(crate) fn start(self) -> MetricsReporter { // TODO: should we do anything with JoinHandle(s) returned by start methods? - self.receiver.start(handle); - self.sender.start(handle); + self.receiver.start(); + self.sender.start(); self.reporter } } diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index ae7e5e1d055..f6c6043af82 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -13,33 +13,31 @@ // limitations under the License. use crate::config::Config; -use crate::node::packet_processing::PacketProcessor; +use crate::node::listener::connection_handler::packet_processing::PacketProcessor; +use crate::node::listener::connection_handler::ConnectionHandler; +use crate::node::listener::Listener; use crypto::asymmetric::encryption; use directory_client::DirectoryClient; -use futures::channel::mpsc; use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use std::sync::Arc; use tokio::runtime::Runtime; mod listener; mod metrics; -mod packet_forwarding; -pub(crate) mod packet_processing; mod presence; // the MixNode will live for whole duration of this program pub struct MixNode { - runtime: Runtime, config: Config, - sphinx_keypair: encryption::KeyPair, + sphinx_keypair: Arc, } impl MixNode { pub fn new(config: Config, sphinx_keypair: encryption::KeyPair) -> Self { MixNode { - runtime: Runtime::new().unwrap(), config, - sphinx_keypair, + sphinx_keypair: Arc::new(sphinx_keypair), } } @@ -53,7 +51,7 @@ impl MixNode { self.config.get_layer(), self.config.get_presence_sending_delay(), ); - presence::Notifier::new(notifier_config).start(self.runtime.handle()); + presence::Notifier::new(notifier_config).start(); } fn start_metrics_reporter(&self) -> metrics::MetricsReporter { @@ -64,51 +62,47 @@ impl MixNode { self.config.get_metrics_sending_delay(), self.config.get_metrics_running_stats_logging_delay(), ) - .start(self.runtime.handle()) + .start() } fn start_socket_listener( &self, metrics_reporter: metrics::MetricsReporter, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, + forwarding_channel: MixForwardingSender, ) { info!("Starting socket listener..."); - // this is the only location where our private key is going to be copied - // it will be held in memory owned by `MixNode` and inside an Arc of `PacketProcessor` - let packet_processor = - PacketProcessor::new(self.sphinx_keypair.private_key().clone(), metrics_reporter); - - listener::run_socket_listener( - self.runtime.handle(), - self.config.get_listening_address(), - packet_processor, - forwarding_channel, + + let packet_processor = PacketProcessor::new( + self.sphinx_keypair.private_key(), + metrics_reporter, + self.config.get_cache_entry_ttl(), ); + + let connection_handler = ConnectionHandler::new(packet_processor, forwarding_channel); + + let listener = Listener::new(self.config.get_listening_address()); + + listener.start(connection_handler); } - fn start_packet_forwarder( - &mut self, - ) -> mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)> { + fn start_packet_forwarder(&mut self) -> MixForwardingSender { info!("Starting packet forwarder..."); - self.runtime - .enter(|| { - packet_forwarding::PacketForwarder::new( - self.config.get_packet_forwarding_initial_backoff(), - self.config.get_packet_forwarding_maximum_backoff(), - self.config.get_initial_connection_timeout(), - ) - }) - .start(self.runtime.handle()) + + let (mut packet_forwarder, packet_sender) = PacketForwarder::new( + self.config.get_packet_forwarding_initial_backoff(), + self.config.get_packet_forwarding_maximum_backoff(), + self.config.get_initial_connection_timeout(), + ); + + tokio::spawn(async move { packet_forwarder.run().await }); + packet_sender } - fn check_if_same_ip_node_exists(&mut self) -> Option { + async fn check_if_same_ip_node_exists(&mut self) -> Option { let directory_client_config = directory_client::Config::new(self.config.get_presence_directory_server()); let directory_client = directory_client::Client::new(directory_client_config); - let topology = self - .runtime - .block_on(directory_client.get_topology()) - .ok()?; + let topology = directory_client.get_topology().await.ok()?; let existing_mixes_presence = topology.mix_nodes; existing_mixes_presence .iter() @@ -116,32 +110,38 @@ impl MixNode { .map(|node| node.pub_key.clone()) } - pub fn run(&mut self) { - info!("Starting nym mixnode"); - - if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists() { - error!( - "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", - duplicate_node_key - ); - return; - } - let forwarding_channel = self.start_packet_forwarder(); - let metrics_reporter = self.start_metrics_reporter(); - self.start_socket_listener(metrics_reporter, forwarding_channel); - self.start_presence_notifier(); - - info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); - - if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) { + async fn wait_for_interrupt(&self) { + if let Err(e) = tokio::signal::ctrl_c().await { error!( "There was an error while capturing SIGINT - {:?}. We will terminate regardless", e ); } - println!( "Received SIGINT - the mixnode will terminate now (threads are not YET nicely stopped)" ); } + + pub fn run(&mut self) { + info!("Starting nym mixnode"); + let mut runtime = Runtime::new().unwrap(); + + runtime.block_on(async { + if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await { + error!( + "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + duplicate_node_key + ); + return; + } + let forwarding_channel = self.start_packet_forwarder(); + let metrics_reporter = self.start_metrics_reporter(); + self.start_socket_listener(metrics_reporter, forwarding_channel); + self.start_presence_notifier(); + + info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); + + self.wait_for_interrupt().await + }) + } } diff --git a/mixnode/src/node/packet_forwarding.rs b/mixnode/src/node/packet_forwarding.rs deleted file mode 100644 index 29a112d442f..00000000000 --- a/mixnode/src/node/packet_forwarding.rs +++ /dev/null @@ -1,65 +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 futures::channel::mpsc; -use futures::StreamExt; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::time::Duration; -use tokio::runtime::Handle; - -pub(crate) struct PacketForwarder { - tcp_client: mixnet_client::Client, - conn_tx: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, - conn_rx: mpsc::UnboundedReceiver<(NymNodeRoutingAddress, SphinxPacket)>, -} - -impl PacketForwarder { - pub(crate) fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> PacketForwarder { - let tcp_client_config = mixnet_client::Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ); - - let (conn_tx, conn_rx) = mpsc::unbounded(); - - PacketForwarder { - tcp_client: mixnet_client::Client::new(tcp_client_config), - conn_tx, - conn_rx, - } - } - - pub(crate) fn start( - mut self, - handle: &Handle, - ) -> mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)> { - // TODO: what to do with the lost JoinHandle? - let sender_channel = self.conn_tx.clone(); - handle.spawn(async move { - while let Some((address, packet)) = self.conn_rx.next().await { - trace!("Going to forward packet to {:?}", address); - // as a mix node we don't care about responses, we just want to fire packets - // as quickly as possible - self.tcp_client.send(address, packet, false).await.unwrap(); // if we're not waiting for response, we MUST get an Ok - } - }); - sender_channel - } -} diff --git a/mixnode/src/node/packet_processing.rs b/mixnode/src/node/packet_processing.rs deleted file mode 100644 index f0756482180..00000000000 --- a/mixnode/src/node/packet_processing.rs +++ /dev/null @@ -1,114 +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::node::metrics; -use crypto::asymmetric::encryption; -use log::*; -use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nymsphinx::{ - Delay as SphinxDelay, Error as SphinxError, NodeAddressBytes, ProcessedPacket, SphinxPacket, -}; -use std::convert::TryFrom; -use std::sync::Arc; - -#[derive(Debug)] -pub enum MixProcessingError { - ReceivedFinalHopError, - SphinxProcessingError(SphinxError), - InvalidHopAddress, -} - -pub enum MixProcessingResult { - ForwardHop(NymNodeRoutingAddress, SphinxPacket), - #[allow(dead_code)] - LoopMessage, -} - -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; - - SphinxProcessingError(err) - } -} - -impl From for MixProcessingError { - fn from(_: NymNodeRoutingAddressError) -> Self { - use MixProcessingError::*; - - InvalidHopAddress - } -} - -// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets -#[derive(Clone)] -pub struct PacketProcessor { - secret_key: Arc, - metrics_reporter: metrics::MetricsReporter, -} - -impl PacketProcessor { - pub(crate) fn new( - secret_key: encryption::PrivateKey, - metrics_reporter: metrics::MetricsReporter, - ) -> Self { - PacketProcessor { - secret_key: Arc::new(secret_key), - metrics_reporter, - } - } - - pub(crate) fn report_sent(&self, addr: NymNodeRoutingAddress) { - self.metrics_reporter.report_sent(addr.to_string()) - } - - async fn process_forward_hop( - &self, - packet: SphinxPacket, - forward_address: NodeAddressBytes, - delay: SphinxDelay, - ) -> Result { - let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; - - // Delay packet for as long as required - tokio::time::delay_for(delay.to_duration()).await; - - Ok(MixProcessingResult::ForwardHop(next_hop_address, packet)) - } - - pub(crate) async fn process_sphinx_packet( - &self, - packet: SphinxPacket, - ) -> Result { - // we received something resembling a sphinx packet, report it! - self.metrics_reporter.report_received(); - match packet.process(&self.secret_key.as_ref().into()) { - Ok(ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay)) => { - self.process_forward_hop(packet, address, delay).await - } - Ok(ProcessedPacket::ProcessedPacketFinalHop(_, _, _)) => { - warn!("Received a loop cover message that we haven't implemented yet!"); - Err(MixProcessingError::ReceivedFinalHopError) - } - Err(e) => { - warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError(e)) - } - } - } -} - -// TODO: the test that definitely needs to be written is as follows: -// we are stuck trying to write to mix A, can we still forward just fine to mix B? diff --git a/mixnode/src/node/presence.rs b/mixnode/src/node/presence.rs index e31abd06ab6..0067a494b0c 100644 --- a/mixnode/src/node/presence.rs +++ b/mixnode/src/node/presence.rs @@ -17,7 +17,6 @@ use directory_client::presence::mixnodes::MixNodePresence; use directory_client::DirectoryClient; use log::{error, trace}; use std::time::Duration; -use tokio::runtime::Handle; use tokio::task::JoinHandle; pub(crate) struct NotifierConfig { @@ -87,8 +86,8 @@ impl Notifier { } } - pub fn start(self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { + pub fn start(self) -> JoinHandle<()> { + tokio::spawn(async move { loop { // set the deadline in the future let sending_delay = tokio::time::delay_for(self.sending_delay);