Compare commits
204 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f57e485ceb | |||
| 0531b716cc | |||
| bfb91c58c8 | |||
| 57eab1bc85 | |||
| 6fb438a994 | |||
| 23e0aee0a1 | |||
| 2f98568edf | |||
| 2b2bd5b688 | |||
| d05924e070 | |||
| e02c90457e | |||
| 6f3d7b8f49 | |||
| 712ca4bbb2 | |||
| 62b137b3b9 | |||
| 6b508e829f | |||
| 4e7bd2d88f | |||
| b584316caa | |||
| 220ef1b0ef | |||
| 38e747104e | |||
| 4fc2e3bc0d | |||
| 7f3ea6af1a | |||
| fc7635dc27 | |||
| e8b6cabfe3 | |||
| d29ea67f9b | |||
| c241ecfa36 | |||
| 3553f88909 | |||
| 6f5b44d016 | |||
| d5a0e7d355 | |||
| c3ce96afe9 | |||
| f15588f617 | |||
| 0bb6959755 | |||
| efb4660fe7 | |||
| 27c61cb194 | |||
| cddc61fe57 | |||
| 489ea16749 | |||
| b59b9fa4a5 | |||
| 57d2cab98a | |||
| 5c697a3a4d | |||
| 21f0c3e2ac | |||
| 42ab21c133 | |||
| a39eed123e | |||
| 1954895e16 | |||
| f6ad70bff9 | |||
| b9747669d3 | |||
| cbac85fbf4 | |||
| 76f503a664 | |||
| a8eb5ec4c2 | |||
| 56a8118ea3 | |||
| 234bf7d0a0 | |||
| ac8f04959e | |||
| 016f5829a5 | |||
| d79ab40988 | |||
| f0a1ce50bc | |||
| ef82d4327c | |||
| 8da639cd11 | |||
| 428f202cbc | |||
| 073ef2776e | |||
| 091104be8f | |||
| 8bbe771229 | |||
| 0ead20764b | |||
| 8f451283dc | |||
| 6ae5142166 | |||
| a8e46d34df | |||
| 20fe59b9ba | |||
| d0902218d4 | |||
| 38517e41ba | |||
| 507f37fcec | |||
| 06c645cf56 | |||
| b63494f686 | |||
| f78ce3f9b6 | |||
| 671e55f4d5 | |||
| 85d147ef62 | |||
| cb69182e02 | |||
| 8dd8873a03 | |||
| 4e55c6c395 | |||
| 381e85f0bc | |||
| 8ef95ac522 | |||
| 59133ed862 | |||
| 65aa1ba29d | |||
| c71f494974 | |||
| ae25f335f4 | |||
| 3101bb6980 | |||
| 4a241bb59a | |||
| 63f30bf9d0 | |||
| b39ccf26b6 | |||
| 21834fde41 | |||
| b6db1bf1af | |||
| 0b3ce4739b | |||
| f262b2924b | |||
| 724af12e03 | |||
| 3a59f3cef9 | |||
| 6aba3f08f1 | |||
| 8cd2be7edc | |||
| d768eff303 | |||
| ddf0d762d7 | |||
| aa207e2c13 | |||
| c192afe9eb | |||
| 415ba8185e | |||
| fdbb2549f8 | |||
| 3f5e219b1c | |||
| e6350a0ea4 | |||
| 56858c4109 | |||
| 509b222052 | |||
| 29e1488714 | |||
| 37a493439d | |||
| ab02eec824 | |||
| efb900a538 | |||
| 9d969904f2 | |||
| 2b5b61ea26 | |||
| 287d2b31a5 | |||
| de63fff1a1 | |||
| d2f8ffdc2b | |||
| 4143e53598 | |||
| 265ef22838 | |||
| ca28ce9e19 | |||
| f0cf92b542 | |||
| aceb6df848 | |||
| ec849cfc0c | |||
| 233dc5ce9d | |||
| e4cfab537e | |||
| 9fc01cee73 | |||
| 842c0b10b9 | |||
| f10a403045 | |||
| 3ab41835a2 | |||
| 0b41badab0 | |||
| 028cc66db3 | |||
| 60dc3b4650 | |||
| 3d9181a913 | |||
| 1633f54f4f | |||
| 86d27b06df | |||
| 07bb0a69fa | |||
| 138814f26c | |||
| 06540f5ce1 | |||
| 4feadb6352 | |||
| fdf49ff804 | |||
| eb49ef1013 | |||
| 17deec7720 | |||
| 76c2eac3bd | |||
| f095dd114e | |||
| 5004f6d07a | |||
| 31fbe3c265 | |||
| 478fe628ca | |||
| fbb675e815 | |||
| 96f5d45877 | |||
| 5d356847fa | |||
| 16ddb50159 | |||
| 48bead3ac9 | |||
| 35cff8dfcd | |||
| 2850c71317 | |||
| 534a4aedc7 | |||
| 27b55a7e60 | |||
| dc5660728e | |||
| 86693ab983 | |||
| 10d31abbf8 | |||
| c4779e3428 | |||
| 8bb462bda1 | |||
| 427b83875a | |||
| 8393910016 | |||
| e283d162e5 | |||
| d869d427e4 | |||
| a08bbc98db | |||
| c02665f71e | |||
| 9ddbaf42d5 | |||
| bc77ddadfa | |||
| 688376f757 | |||
| 1c0eab2fa2 | |||
| fb456c0bd0 | |||
| 8d0afbd21e | |||
| 528ba87492 | |||
| d08a9965c8 | |||
| bf360473d5 | |||
| fd2f565af2 | |||
| 7a3f16548b | |||
| 6fdc4a6434 | |||
| b5c5ea3193 | |||
| 1371b94599 | |||
| 2c986efcb1 | |||
| 9b8b0bff1d | |||
| cd94ecde19 | |||
| 385e41a920 | |||
| 836cff226a | |||
| 86861ccb70 | |||
| 0b4f10cfe6 | |||
| 8ac76cb152 | |||
| 818389839f | |||
| 3a069f18e2 | |||
| 31d0aa7fe4 | |||
| 256afa3be9 | |||
| 777c40bd97 | |||
| 9deaad8a2a | |||
| 1ad60582ba | |||
| 2aaf2465e4 | |||
| 0fb9c352d6 | |||
| ad289e531b | |||
| 2fbf2456ec | |||
| 2c2ecfb785 | |||
| 9478460893 | |||
| 6ae04b7ed3 | |||
| fff78cfade | |||
| e8472e07f9 | |||
| 2c0a4b11db | |||
| 1eee313e6e | |||
| 14dfa4795a | |||
| d7ed6b5a13 | |||
| f4a62fd64c |
+5
-2
@@ -1,3 +1,6 @@
|
||||
/target
|
||||
/targetString
|
||||
**/*.rs.bk
|
||||
/.idea/
|
||||
/*/target
|
||||
/test_inbox
|
||||
.idea
|
||||
target
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
# Nym Client
|
||||
## The Nym Privacy Platform
|
||||
|
||||
The Nym Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
|
||||
This repository contains the full Nym platform, written in Rust.
|
||||
|
||||
It needs to handle communication with 3 types of remote nodes. Here's the development status of each:
|
||||
The platform is composed of multiple Rust crates. Top-level crates include:
|
||||
|
||||
- [ ] Directory nodes
|
||||
- [ ] Mix nodes
|
||||
- [ ] Validator nodes
|
||||
* client - an executable crate which you can use for interacting with Nym nodes
|
||||
* mixnode - an executable mixnode crate
|
||||
* sfw-provider - an executable store-and-forward provider crate. The provider acts sort of like a mailbox for mixnet messages.
|
||||
|
||||
### Building
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
* Rust 1.39 or later. Stable works.
|
||||
* The `nym` platform repo (this one).
|
||||
* Checkout the [Sphinx]() repo beside the `nym` repo.
|
||||
|
||||
Your directory structure should look like this:
|
||||
|
||||
```
|
||||
tree -L 1
|
||||
├── nym
|
||||
│ ├── client
|
||||
│ ├── mixnode
|
||||
│ ├── README.md
|
||||
│ └── sfw-provider
|
||||
├── sphinx
|
||||
```
|
||||
|
||||
Entering each top-level crate (`client`, `mixnode` or `sfw-provider`) and doing a `cargo build` will build the software.
|
||||
|
||||
As with any other Rust project, there are other ways to build:
|
||||
|
||||
* `cargo build --release` will build an optimized release version for use in production
|
||||
* `cargo test` will run unit and integration tests for the crate (once)
|
||||
* `cargo watch -x test` will run tests whenever you change a file in the crate. Very handy in development.
|
||||
|
||||
Generated
@@ -24,8 +24,8 @@ rand_distr = "0.2.2"
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
sphinx = { path = "../sphinx" }
|
||||
sfw-provider-requests = { path = "../nym-sfw-provider/sfw-provider-requests" }
|
||||
sphinx = { path = "../../sphinx" }
|
||||
sfw-provider-requests = { path = "../sfw-provider/sfw-provider-requests" }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tungstenite = "0.9.2"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Nym Client
|
||||
|
||||
The Nym Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
|
||||
|
||||
It needs to handle communication with 3 types of remote nodes. Here's the development status of each:
|
||||
|
||||
- [ ] Directory nodes
|
||||
- [ ] Mix nodes
|
||||
- [ ] Validator nodes
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# nym-mixnode Changelog
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* removed the `--local` flag
|
||||
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
|
||||
* the `host` argument is now required
|
||||
* IPv6 support
|
||||
* mixnode version number is now shown at node start
|
||||
* directory server location is now shown at node start
|
||||
|
||||
## 0.1.0 - Initial Release
|
||||
|
||||
* The bare minimum set of features required by a Nym Mixnode
|
||||
Generated
+2371
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-mixnode"
|
||||
version = "0.2.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.11.0"
|
||||
clap = "2.33.0"
|
||||
curve25519-dalek = "1.2.3"
|
||||
futures = "0.3.1"
|
||||
nym-client = { path = "../client" }
|
||||
sphinx = { path = "../../sphinx" }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
@@ -0,0 +1,27 @@
|
||||
# Nym Mixnode
|
||||
|
||||
A Rust mixnode implementation.
|
||||
|
||||
## Building
|
||||
|
||||
* check out the code
|
||||
* [install rust](https://www.rust-lang.org/tools/install) (stable)
|
||||
* `cargo build --release` (for a production build)
|
||||
|
||||
The built binary can be found at `target/release/nym-mixnode`
|
||||
|
||||
## Usage
|
||||
|
||||
* `nym-mixnode` prints a help message showing usage options
|
||||
* `nym-mixnode run --help` prints a help message showing usage options for the run command
|
||||
* `nym-mixnode run --layer 1` will start the mixnode in layer 1 (coordinate with other people to find out which layer you need to start yours in)
|
||||
|
||||
By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option.
|
||||
|
||||
### Dependencies
|
||||
|
||||
It's important to download the following repositories before building the mixnode:
|
||||
|
||||
* https://github.com/nymtech/nym-client
|
||||
* https://github.com/nymtech/nym-sfw-provider
|
||||
* https://github.com/nymtech/sphinx
|
||||
@@ -0,0 +1,5 @@
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use std::process;
|
||||
|
||||
mod mix_peer;
|
||||
mod node;
|
||||
|
||||
fn main() {
|
||||
let arg_matches = App::new("Nym Mixnode")
|
||||
.version("0.1.0")
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Loopix-based Mixnode")
|
||||
.subcommand(
|
||||
SubCommand::with_name("run")
|
||||
.about("Starts the mixnode")
|
||||
.arg(
|
||||
Arg::with_name("host")
|
||||
.long("host")
|
||||
.help("The custom host on which the mixnode will be running")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.long("port")
|
||||
.help("The port on which the mixnode will be listening")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("layer")
|
||||
.long("layer")
|
||||
.help("The mixnet layer of this particular node")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the node is sending presence and metrics to")
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Err(e) = execute(arg_matches) {
|
||||
println!("{}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod built_info {
|
||||
// The file has been placed there by the build script.
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) -> Result<(), String> {
|
||||
match matches.subcommand() {
|
||||
("run", Some(m)) => Ok(node::runner::start(m)),
|
||||
_ => Err(usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
banner() + "usage: --help to see available options.\n\n"
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
format!(
|
||||
r#"
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(mixnode - version {:})
|
||||
|
||||
"#,
|
||||
built_info::PKG_VERSION
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use nym_client::utils::addressing;
|
||||
use std::convert::TryInto;
|
||||
use std::error::Error;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use tokio::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MixPeer {
|
||||
connection: SocketAddr,
|
||||
}
|
||||
|
||||
impl MixPeer {
|
||||
// note that very soon `next_hop_address` will be changed to `next_hop_metadata`
|
||||
pub fn new(next_hop_address: [u8; 32]) -> MixPeer {
|
||||
let next_hop_socket_address =
|
||||
addressing::socket_address_from_encoded_bytes(next_hop_address);
|
||||
MixPeer {
|
||||
connection: next_hop_socket_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&self, bytes: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
let next_hop_address = self.connection.clone();
|
||||
let mut stream = tokio::net::TcpStream::connect(next_hop_address).await?;
|
||||
stream.write_all(&bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.connection.to_string()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use nym_client::clients::directory;
|
||||
use nym_client::clients::directory::metrics::MixMetric;
|
||||
use nym_client::clients::directory::requests::metrics_mixes_post::MetricsMixPoster;
|
||||
use nym_client::clients::directory::DirectoryClient;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
const METRICS_INTERVAL: u64 = 3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetricsReporter {
|
||||
received: u64,
|
||||
sent: HashMap<String, u64>,
|
||||
}
|
||||
|
||||
impl MetricsReporter {
|
||||
pub(crate) fn new() -> Self {
|
||||
MetricsReporter {
|
||||
received: 0,
|
||||
sent: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_arc_mutex(self) -> Arc<Mutex<Self>> {
|
||||
Arc::new(Mutex::new(self))
|
||||
}
|
||||
|
||||
async fn increment_received_metrics(metrics: Arc<Mutex<MetricsReporter>>) {
|
||||
let mut unlocked = metrics.lock().await;
|
||||
unlocked.received += 1;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_received_metrics_control(
|
||||
metrics: Arc<Mutex<MetricsReporter>>,
|
||||
mut rx: mpsc::Receiver<()>,
|
||||
) {
|
||||
while let Some(_) = rx.next().await {
|
||||
MetricsReporter::increment_received_metrics(metrics.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn increment_sent_metrics(metrics: Arc<Mutex<MetricsReporter>>, sent_to: String) {
|
||||
let mut unlocked = metrics.lock().await;
|
||||
let receiver_count = unlocked.sent.entry(sent_to).or_insert(0);
|
||||
*receiver_count += 1;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_sent_metrics_control(
|
||||
metrics: Arc<Mutex<MetricsReporter>>,
|
||||
mut rx: mpsc::Receiver<String>,
|
||||
) {
|
||||
while let Some(sent_metric) = rx.next().await {
|
||||
MetricsReporter::increment_sent_metrics(metrics.clone(), sent_metric).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn acquire_and_reset_metrics(
|
||||
metrics: Arc<Mutex<MetricsReporter>>,
|
||||
) -> (u64, HashMap<String, u64>) {
|
||||
let mut unlocked = metrics.lock().await;
|
||||
let received = unlocked.received;
|
||||
|
||||
let sent = std::mem::replace(&mut unlocked.sent, HashMap::new());
|
||||
unlocked.received = 0;
|
||||
|
||||
(received, sent)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_metrics_sender(
|
||||
metrics: Arc<Mutex<MetricsReporter>>,
|
||||
cfg: directory::Config,
|
||||
pub_key_str: String,
|
||||
) {
|
||||
let delay_duration = Duration::from_secs(METRICS_INTERVAL);
|
||||
let directory_client = directory::Client::new(cfg);
|
||||
loop {
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
let (received, sent) =
|
||||
MetricsReporter::acquire_and_reset_metrics(metrics.clone()).await;
|
||||
directory_client
|
||||
.metrics_post
|
||||
.post(&MixMetric {
|
||||
pub_key: pub_key_str.clone(),
|
||||
received,
|
||||
sent,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use sphinx::header::delays::Delay as SphinxDelay;
|
||||
use sphinx::{ProcessedPacket, SphinxPacket};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::mix_peer;
|
||||
use crate::mix_peer::MixPeer;
|
||||
use crate::node::metrics::MetricsReporter;
|
||||
use futures::SinkExt;
|
||||
use nym_client::clients::directory;
|
||||
|
||||
mod metrics;
|
||||
mod presence;
|
||||
pub mod runner;
|
||||
|
||||
pub struct Config {
|
||||
directory_server: String,
|
||||
layer: usize,
|
||||
public_key: MontgomeryPoint,
|
||||
secret_key: Scalar,
|
||||
socket_address: SocketAddr,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn public_key_string(&self) -> String {
|
||||
let key_bytes = self.public_key.to_bytes().to_vec();
|
||||
base64::encode_config(&key_bytes, base64::URL_SAFE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MixProcessingError {
|
||||
InvalidPeerAddressError,
|
||||
SphinxRecoveryError,
|
||||
ReceivedFinalHopError,
|
||||
}
|
||||
|
||||
impl From<sphinx::ProcessingError> for MixProcessingError {
|
||||
// for time being just have a single error instance for all possible results of sphinx::ProcessingError
|
||||
fn from(_: sphinx::ProcessingError) -> Self {
|
||||
use MixProcessingError::*;
|
||||
|
||||
SphinxRecoveryError
|
||||
}
|
||||
}
|
||||
|
||||
struct ForwardingData {
|
||||
packet: SphinxPacket,
|
||||
delay: SphinxDelay,
|
||||
recipient: MixPeer,
|
||||
sent_metrics_tx: mpsc::Sender<String>,
|
||||
}
|
||||
|
||||
// TODO: this will need to be changed if MixPeer will live longer than our Forwarding Data
|
||||
impl ForwardingData {
|
||||
fn new(
|
||||
packet: SphinxPacket,
|
||||
delay: SphinxDelay,
|
||||
recipient: MixPeer,
|
||||
sent_metrics_tx: mpsc::Sender<String>,
|
||||
) -> Self {
|
||||
ForwardingData {
|
||||
packet,
|
||||
delay,
|
||||
recipient,
|
||||
sent_metrics_tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessingData defines all data required to correctly unwrap sphinx packets
|
||||
struct ProcessingData {
|
||||
secret_key: Scalar,
|
||||
received_metrics_tx: mpsc::Sender<()>,
|
||||
sent_metrics_tx: mpsc::Sender<String>,
|
||||
}
|
||||
|
||||
impl ProcessingData {
|
||||
fn new(
|
||||
secret_key: Scalar,
|
||||
received_metrics_tx: mpsc::Sender<()>,
|
||||
sent_metrics_tx: mpsc::Sender<String>,
|
||||
) -> Self {
|
||||
ProcessingData {
|
||||
secret_key,
|
||||
received_metrics_tx,
|
||||
sent_metrics_tx,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_arc_mutex(self) -> Arc<Mutex<Self>> {
|
||||
Arc::new(Mutex::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
struct PacketProcessor;
|
||||
|
||||
impl PacketProcessor {
|
||||
pub async fn process_sphinx_data_packet(
|
||||
packet_data: &[u8],
|
||||
processing_data: Arc<Mutex<ProcessingData>>,
|
||||
) -> Result<ForwardingData, MixProcessingError> {
|
||||
// we received something resembling a sphinx packet, report it!
|
||||
let processing_data = processing_data.lock().await;
|
||||
let mut received_sender = processing_data.received_metrics_tx.clone();
|
||||
|
||||
received_sender.send(()).await.unwrap();
|
||||
|
||||
let packet = SphinxPacket::from_bytes(packet_data.to_vec())?;
|
||||
let (next_packet, next_hop_address, delay) =
|
||||
match packet.process(processing_data.secret_key) {
|
||||
ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay) => {
|
||||
(packet, address, delay)
|
||||
}
|
||||
_ => return Err(MixProcessingError::ReceivedFinalHopError),
|
||||
};
|
||||
|
||||
let next_mix = MixPeer::new(next_hop_address);
|
||||
|
||||
let fwd_data = ForwardingData::new(
|
||||
next_packet,
|
||||
delay,
|
||||
next_mix,
|
||||
processing_data.sent_metrics_tx.clone(),
|
||||
);
|
||||
Ok(fwd_data)
|
||||
}
|
||||
|
||||
async fn wait_and_forward(mut forwarding_data: ForwardingData) {
|
||||
let delay_duration = Duration::from_nanos(forwarding_data.delay.get_value());
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
forwarding_data
|
||||
.sent_metrics_tx
|
||||
.send(forwarding_data.recipient.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("RECIPIENT: {:?}", forwarding_data.recipient);
|
||||
match forwarding_data
|
||||
.recipient
|
||||
.send(forwarding_data.packet.to_bytes())
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
println!(
|
||||
"failed to write bytes to next mix peer. err = {:?}",
|
||||
e.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the MixNode will live for whole duration of this program
|
||||
pub struct MixNode {
|
||||
directory_server: String,
|
||||
network_address: SocketAddr,
|
||||
public_key: MontgomeryPoint,
|
||||
secret_key: Scalar,
|
||||
// TODO: use it later to enforce forward travel
|
||||
// layer: usize,
|
||||
}
|
||||
|
||||
impl MixNode {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
MixNode {
|
||||
directory_server: config.directory_server.clone(),
|
||||
network_address: config.socket_address,
|
||||
secret_key: config.secret_key,
|
||||
public_key: config.public_key,
|
||||
// layer: config.layer,
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_socket_connection(
|
||||
mut socket: tokio::net::TcpStream,
|
||||
processing_data: Arc<Mutex<ProcessingData>>,
|
||||
) {
|
||||
// NOTE: processing_data is copied here!!
|
||||
let mut buf = [0u8; sphinx::PACKET_SIZE];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => {
|
||||
println!("Remote connection closed.");
|
||||
return;
|
||||
}
|
||||
Ok(_) => {
|
||||
let fwd_data = PacketProcessor::process_sphinx_data_packet(
|
||||
buf.as_ref(),
|
||||
processing_data.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
PacketProcessor::wait_and_forward(fwd_data).await;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the some data back
|
||||
if let Err(e) = socket.write_all(b"foomp").await {
|
||||
println!("failed to write reply to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create the runtime, probably later move it to MixNode itself?
|
||||
let mut rt = Runtime::new()?;
|
||||
|
||||
let (received_tx, received_rx) = mpsc::channel(1024);
|
||||
let (sent_tx, sent_rx) = mpsc::channel(1024);
|
||||
|
||||
let directory_cfg = directory::Config {
|
||||
base_url: self.directory_server.clone(),
|
||||
};
|
||||
let pub_key_str =
|
||||
base64::encode_config(&self.public_key.to_bytes().to_vec(), base64::URL_SAFE);
|
||||
|
||||
let metrics = MetricsReporter::new().add_arc_mutex();
|
||||
rt.spawn(MetricsReporter::run_received_metrics_control(
|
||||
metrics.clone(),
|
||||
received_rx,
|
||||
));
|
||||
rt.spawn(MetricsReporter::run_sent_metrics_control(
|
||||
metrics.clone(),
|
||||
sent_rx,
|
||||
));
|
||||
rt.spawn(MetricsReporter::run_metrics_sender(
|
||||
metrics,
|
||||
directory_cfg,
|
||||
pub_key_str,
|
||||
));
|
||||
|
||||
// Spawn the root task
|
||||
rt.block_on(async {
|
||||
let mut listener = tokio::net::TcpListener::bind(self.network_address).await?;
|
||||
let processing_data =
|
||||
ProcessingData::new(self.secret_key, received_tx, sent_tx).add_arc_mutex();
|
||||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await?;
|
||||
|
||||
let thread_processing_data = processing_data.clone();
|
||||
tokio::spawn(async move {
|
||||
MixNode::process_socket_connection(socket, thread_processing_data).await;
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::node;
|
||||
use nym_client::clients::directory;
|
||||
use nym_client::clients::directory::presence::MixNodePresence;
|
||||
use nym_client::clients::directory::requests::presence_mixnodes_post::PresenceMixNodesPoster;
|
||||
use nym_client::clients::directory::DirectoryClient;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Notifier {
|
||||
pub net_client: directory::Client,
|
||||
presence: MixNodePresence,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(node_config: &node::Config) -> Notifier {
|
||||
let config = directory::Config {
|
||||
base_url: node_config.directory_server.clone(),
|
||||
};
|
||||
let net_client = directory::Client::new(config);
|
||||
let presence = MixNodePresence {
|
||||
host: node_config.socket_address.to_string(), // note: the directory server determines the real incoming IP itself, but uses the socket. Host here is just a placeholder.
|
||||
pub_key: node_config.public_key_string(),
|
||||
layer: node_config.layer as u64,
|
||||
last_seen: 0,
|
||||
};
|
||||
Notifier {
|
||||
net_client,
|
||||
presence,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self) {
|
||||
self.net_client
|
||||
.presence_mix_nodes_post
|
||||
.post(&self.presence)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
loop {
|
||||
self.notify();
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use crate::banner;
|
||||
use crate::node;
|
||||
use crate::node::presence;
|
||||
use crate::node::MixNode;
|
||||
use clap::ArgMatches;
|
||||
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::thread;
|
||||
|
||||
pub fn start(matches: &ArgMatches) {
|
||||
println!("{}", banner());
|
||||
println!("Starting mixnode...");
|
||||
|
||||
let config = new_config(matches);
|
||||
println!("Public key: {}", config.public_key_string());
|
||||
println!("Directory server: {}", config.directory_server);
|
||||
println!(
|
||||
"Listening for incoming packets on {}",
|
||||
config.socket_address
|
||||
);
|
||||
|
||||
let mix = MixNode::new(&config);
|
||||
thread::spawn(move || {
|
||||
let notifier = presence::Notifier::new(&config);
|
||||
notifier.run();
|
||||
});
|
||||
mix.start().unwrap();
|
||||
}
|
||||
|
||||
fn new_config(matches: &ArgMatches) -> node::Config {
|
||||
let host = matches.value_of("host").unwrap();
|
||||
|
||||
let port = match matches.value_of("port").unwrap_or("1789").parse::<u16>() {
|
||||
Ok(n) => n,
|
||||
Err(err) => panic!("Invalid port value provided - {:?}", err),
|
||||
};
|
||||
|
||||
let layer = match matches.value_of("layer").unwrap().parse::<usize>() {
|
||||
Ok(n) => n,
|
||||
Err(err) => panic!("Invalid layer value provided - {:?}", err),
|
||||
};
|
||||
|
||||
let socket_address = (host, port)
|
||||
.to_socket_addrs()
|
||||
.expect("Failed to combine host and port")
|
||||
.next()
|
||||
.expect("Failed to extract the socket address from the iterator");
|
||||
|
||||
let (secret_key, public_key) = sphinx::crypto::keygen();
|
||||
|
||||
let directory_server = matches
|
||||
.value_of("directory")
|
||||
.unwrap_or("https://directory.nymtech.net")
|
||||
.to_string();
|
||||
|
||||
node::Config {
|
||||
directory_server,
|
||||
layer,
|
||||
public_key,
|
||||
socket_address,
|
||||
secret_key,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# nym-sfw-provider Changelog
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* removed the `--local` flag
|
||||
* introduced `--directory` argument to support arbitrary directory servers. Leaving it out will point the node at the "https://directory.nymtech.net" alpha testnet server
|
||||
* IPv6 support
|
||||
* provider version number is now shown at node start
|
||||
* directory server location is now shown at node start
|
||||
|
||||
## 0.1.0 - Initial Release
|
||||
|
||||
* The bare minimum set of features required by a Nym Store and Forward Provider
|
||||
Generated
+2378
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-sfw-provider"
|
||||
version = "0.2.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.11.0"
|
||||
clap = "2.33.0"
|
||||
curve25519-dalek = "1.2.3"
|
||||
hex = "0.4.0"
|
||||
futures = "0.3.1"
|
||||
nym-client = { path = "../client" }
|
||||
rand = "0.7.2"
|
||||
sfw-provider-requests = { path = "./sfw-provider-requests" }
|
||||
sphinx = { path = "../../sphinx" }
|
||||
tokio = { version = "0.2.4", features = ["full"] }
|
||||
sha2 = "0.8.0"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
hmac = "0.7.1"
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
@@ -0,0 +1,2 @@
|
||||
# nym-sfw-provider
|
||||
A store-and-forward provider for Nym, implemented in Rust
|
||||
@@ -0,0 +1,5 @@
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[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 = "arrayref"
|
||||
version = "0.3.5"
|
||||
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 = "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 = "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 = "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.48"
|
||||
source = "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 = "clear_on_drop"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.48 (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 = "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 = "fake-simd"
|
||||
version = "0.1.2"
|
||||
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 = "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.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.7.0 (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 = "keystream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
source = "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 = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
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 = "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.66 (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.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.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_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.66 (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 = "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 = "sfw-provider-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sphinx 0.1.0",
|
||||
]
|
||||
|
||||
[[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 = "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 = "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 = "typenum"
|
||||
version = "1.11.2"
|
||||
source = "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.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-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"
|
||||
|
||||
[metadata]
|
||||
"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 arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"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 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 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 c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
|
||||
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
|
||||
"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 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 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 fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"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 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 keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum lioness 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
|
||||
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
|
||||
"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.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
||||
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
|
||||
"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 typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"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"
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "sfw-provider-requests"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <jedrzej.stuczynski@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
sphinx = { path = "../../../sphinx" }
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
|
||||
pub const DUMMY_MESSAGE_CONTENT: &[u8] =
|
||||
b"[DUMMY MESSAGE] Wanting something does not give you the right to have it.";
|
||||
|
||||
pub type AuthToken = [u8; 32];
|
||||
@@ -0,0 +1,231 @@
|
||||
use crate::AuthToken;
|
||||
use sphinx::route::DestinationAddressBytes;
|
||||
|
||||
const PULL_REQUEST_MESSAGE_PREFIX: [u8; 2] = [1, 0];
|
||||
const REGISTER_MESSAGE_PREFIX: [u8; 2] = [0, 1];
|
||||
|
||||
// TODO: how to do it more nicely, considering all sfw-provider-requests implement same trait that is exercised here?
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderRequests {
|
||||
PullMessages(PullRequest),
|
||||
Register(RegisterRequest),
|
||||
}
|
||||
|
||||
impl ProviderRequests {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
use ProviderRequests::*;
|
||||
match self {
|
||||
PullMessages(pr) => pr.to_bytes(),
|
||||
Register(pr) => pr.to_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderRequestError> {
|
||||
use ProviderRequests::*;
|
||||
if bytes.len() < 2 {
|
||||
return Err(ProviderRequestError::UnmarshalError);
|
||||
}
|
||||
let mut received_prefix = [0; 2];
|
||||
received_prefix.copy_from_slice(&bytes[..2]);
|
||||
match received_prefix {
|
||||
PULL_REQUEST_MESSAGE_PREFIX => Ok(PullMessages(PullRequest::from_bytes(bytes)?)),
|
||||
REGISTER_MESSAGE_PREFIX => Ok(Register(RegisterRequest::from_bytes(bytes)?)),
|
||||
_ => Err(ProviderRequestError::UnmarshalErrorIncorrectPrefix),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderRequestError {
|
||||
MarshalError,
|
||||
UnmarshalError,
|
||||
UnmarshalErrorIncorrectPrefix,
|
||||
}
|
||||
|
||||
pub trait ProviderRequest
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn get_prefix() -> [u8; 2];
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderRequestError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PullRequest {
|
||||
// TODO: public keys, signatures, tokens, etc. basically some kind of authentication bs
|
||||
pub auth_token: AuthToken,
|
||||
pub destination_address: sphinx::route::DestinationAddressBytes,
|
||||
}
|
||||
|
||||
impl PullRequest {
|
||||
pub fn new(
|
||||
destination_address: sphinx::route::DestinationAddressBytes,
|
||||
auth_token: AuthToken,
|
||||
) -> Self {
|
||||
PullRequest {
|
||||
auth_token,
|
||||
destination_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProviderRequest for PullRequest {
|
||||
fn get_prefix() -> [u8; 2] {
|
||||
PULL_REQUEST_MESSAGE_PREFIX
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
Self::get_prefix()
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.chain(self.destination_address.iter().cloned())
|
||||
.chain(self.auth_token.iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderRequestError> {
|
||||
if bytes.len() != 2 + 32 + 32 {
|
||||
return Err(ProviderRequestError::UnmarshalError);
|
||||
}
|
||||
|
||||
let mut received_prefix = [0u8; 2];
|
||||
received_prefix.copy_from_slice(&bytes[..2]);
|
||||
if received_prefix != Self::get_prefix() {
|
||||
return Err(ProviderRequestError::UnmarshalErrorIncorrectPrefix);
|
||||
}
|
||||
|
||||
let mut destination_address = [0u8; 32];
|
||||
destination_address.copy_from_slice(&bytes[2..34]);
|
||||
|
||||
let mut auth_token = [0u8; 32];
|
||||
auth_token.copy_from_slice(&bytes[34..]);
|
||||
|
||||
Ok(PullRequest {
|
||||
auth_token,
|
||||
destination_address,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegisterRequest {
|
||||
pub destination_address: DestinationAddressBytes,
|
||||
}
|
||||
|
||||
impl RegisterRequest {
|
||||
pub fn new(destination_address: DestinationAddressBytes) -> Self {
|
||||
RegisterRequest {
|
||||
destination_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProviderRequest for RegisterRequest {
|
||||
fn get_prefix() -> [u8; 2] {
|
||||
REGISTER_MESSAGE_PREFIX
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
Self::get_prefix()
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.chain(self.destination_address.iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderRequestError> {
|
||||
if bytes.len() != 2 + 32 {
|
||||
return Err(ProviderRequestError::UnmarshalError);
|
||||
}
|
||||
|
||||
let mut received_prefix = [0u8; 2];
|
||||
received_prefix.copy_from_slice(&bytes[..2]);
|
||||
if received_prefix != Self::get_prefix() {
|
||||
return Err(ProviderRequestError::UnmarshalErrorIncorrectPrefix);
|
||||
}
|
||||
|
||||
let mut destination_address = [0u8; 32];
|
||||
destination_address.copy_from_slice(&bytes[2..]);
|
||||
|
||||
Ok(RegisterRequest {
|
||||
destination_address,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod creating_pull_request {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_is_possible_to_recover_it_from_bytes() {
|
||||
let address = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
0, 1, 2,
|
||||
];
|
||||
let auth_token = [1u8; 32];
|
||||
let pull_request = PullRequest::new(address, auth_token);
|
||||
let bytes = pull_request.to_bytes();
|
||||
|
||||
let recovered = PullRequest::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(address, recovered.destination_address);
|
||||
assert_eq!(auth_token, recovered.auth_token);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_is_possible_to_recover_it_from_bytes_with_enum_wrapper() {
|
||||
let address = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
0, 1, 2,
|
||||
];
|
||||
let auth_token = [1u8; 32];
|
||||
let pull_request = PullRequest::new(address, auth_token);
|
||||
let bytes = pull_request.to_bytes();
|
||||
|
||||
let recovered = ProviderRequests::from_bytes(&bytes).unwrap();
|
||||
match recovered {
|
||||
ProviderRequests::PullMessages(req) => {
|
||||
assert_eq!(address, req.destination_address);
|
||||
assert_eq!(auth_token, req.auth_token);
|
||||
}
|
||||
_ => panic!("expected to recover pull request!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod creating_register_request {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_is_possible_to_recover_it_from_bytes() {
|
||||
let address = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
0, 1, 2,
|
||||
];
|
||||
let register_request = RegisterRequest::new(address);
|
||||
let bytes = register_request.to_bytes();
|
||||
|
||||
let recovered = RegisterRequest::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(address, recovered.destination_address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_is_possible_to_recover_it_from_bytes_with_enum_wrapper() {
|
||||
let address = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
0, 1, 2,
|
||||
];
|
||||
let register_request = RegisterRequest::new(address);
|
||||
let bytes = register_request.to_bytes();
|
||||
|
||||
let recovered = ProviderRequests::from_bytes(&bytes).unwrap();
|
||||
match recovered {
|
||||
ProviderRequests::Register(req) => {
|
||||
assert_eq!(address, req.destination_address);
|
||||
}
|
||||
_ => panic!("expected to recover pull request!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
use crate::AuthToken;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderResponseError {
|
||||
MarshalError,
|
||||
UnmarshalError,
|
||||
UnmarshalErrorInvalidLength,
|
||||
}
|
||||
|
||||
pub trait ProviderResponse
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderResponseError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PullResponse {
|
||||
pub messages: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegisterResponse {
|
||||
pub auth_token: AuthToken,
|
||||
}
|
||||
|
||||
impl PullResponse {
|
||||
pub fn new(messages: Vec<Vec<u8>>) -> Self {
|
||||
PullResponse { messages }
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterResponse {
|
||||
pub fn new(auth_token: AuthToken) -> Self {
|
||||
RegisterResponse { auth_token }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should go into some kind of utils module/crate
|
||||
fn read_be_u16(input: &mut &[u8]) -> u16 {
|
||||
let (int_bytes, rest) = input.split_at(std::mem::size_of::<u16>());
|
||||
*input = rest;
|
||||
u16::from_be_bytes(int_bytes.try_into().unwrap())
|
||||
}
|
||||
|
||||
// TODO: currently this allows for maximum 64kB payload - if we go over that in sphinx,
|
||||
// we need to update this code.
|
||||
impl ProviderResponse for PullResponse {
|
||||
// num_msgs || len1 || len2 || ... || msg1 || msg2 || ...
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let num_msgs = self.messages.len() as u16;
|
||||
let msgs_lens: Vec<u16> = self.messages.iter().map(|msg| msg.len() as u16).collect();
|
||||
|
||||
num_msgs
|
||||
.to_be_bytes()
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.chain(
|
||||
msgs_lens
|
||||
.into_iter()
|
||||
.flat_map(|len| len.to_be_bytes().to_vec().into_iter()),
|
||||
)
|
||||
.chain(self.messages.iter().flat_map(|msg| msg.clone().into_iter()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderResponseError> {
|
||||
// can we read number of messages?
|
||||
if bytes.len() < 2 {
|
||||
return Err(ProviderResponseError::UnmarshalErrorInvalidLength);
|
||||
}
|
||||
|
||||
let mut bytes_copy = bytes.clone();
|
||||
let num_msgs = read_be_u16(&mut bytes_copy);
|
||||
|
||||
// can we read all lengths of messages?
|
||||
if bytes_copy.len() < (num_msgs * 2) as usize {
|
||||
return Err(ProviderResponseError::UnmarshalErrorInvalidLength);
|
||||
}
|
||||
|
||||
let msgs_lens: Vec<_> = (0..num_msgs)
|
||||
.map(|_| read_be_u16(&mut bytes_copy))
|
||||
.collect();
|
||||
|
||||
let required_remaining_len = msgs_lens
|
||||
.iter()
|
||||
.fold(0usize, |acc, &len| acc + (len as usize));
|
||||
|
||||
// can we read messages themselves?
|
||||
if bytes_copy.len() != required_remaining_len {
|
||||
return Err(ProviderResponseError::UnmarshalErrorInvalidLength);
|
||||
}
|
||||
|
||||
let msgs = msgs_lens
|
||||
.iter()
|
||||
.scan(0usize, |i, &len| {
|
||||
let j = *i + (len as usize);
|
||||
let msg = bytes_copy[*i..j].to_vec();
|
||||
*i = j;
|
||||
Some(msg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(PullResponse { messages: msgs })
|
||||
}
|
||||
}
|
||||
|
||||
impl ProviderResponse for RegisterResponse {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.auth_token.to_vec()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, ProviderResponseError> {
|
||||
match bytes.len() {
|
||||
32 => {
|
||||
let mut auth_token = [0u8; 32];
|
||||
auth_token.copy_from_slice(&bytes[..32]);
|
||||
Ok(RegisterResponse { auth_token })
|
||||
}
|
||||
_ => Err(ProviderResponseError::UnmarshalErrorInvalidLength),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod creating_pull_response {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_is_possible_to_recover_it_from_bytes() {
|
||||
let msg1 = vec![1, 2, 3, 4, 5];
|
||||
let msg2 = vec![];
|
||||
let msg3 = vec![
|
||||
1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4,
|
||||
5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3,
|
||||
4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2,
|
||||
3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1,
|
||||
2, 3, 4, 5, 1, 2, 3, 4, 5,
|
||||
];
|
||||
let msg4 = vec![1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
let msgs = vec![msg1, msg2, msg3, msg4];
|
||||
let pull_response = PullResponse::new(msgs.clone());
|
||||
let bytes = pull_response.to_bytes();
|
||||
|
||||
let recovered = PullResponse::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(msgs, recovered.messages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
use crate::provider::ServiceProvider;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use nym_client::identity::mixnet::KeyPair;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
pub mod provider;
|
||||
|
||||
fn main() {
|
||||
let arg_matches = App::new("Nym Service Provider")
|
||||
.version("0.1.0")
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Loopix-based Service Provider")
|
||||
.subcommand(
|
||||
SubCommand::with_name("run")
|
||||
.about("Starts the service provider")
|
||||
.arg(
|
||||
Arg::with_name("mixHost")
|
||||
.long("mixHost")
|
||||
.help("The custom host on which the service provider will be running for receiving sphinx packets")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("mixPort")
|
||||
.long("mixPort")
|
||||
.help("The port on which the service provider will be listening for sphinx packets")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clientHost")
|
||||
.long("clientHost")
|
||||
.help("The custom host on which the service provider will be running for receiving client sfw-provider-requests")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clientPort")
|
||||
.long("clientPort")
|
||||
.help("The port on which the service provider will be listening for client sfw-provider-requests")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storeDir")
|
||||
.short("s")
|
||||
.long("storeDir")
|
||||
.help("Directory storing all packets for the clients")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("registeredLedger")
|
||||
.short("r")
|
||||
.long("registeredLedger")
|
||||
.help("Directory of the ledger of registered clients")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the node is sending presence and metrics to")
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Err(e) = execute(arg_matches) {
|
||||
println!("{}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(matches: &ArgMatches) {
|
||||
let config = new_config(matches);
|
||||
let provider = ServiceProvider::new(&config);
|
||||
|
||||
provider.start().unwrap()
|
||||
}
|
||||
|
||||
fn new_config(matches: &ArgMatches) -> provider::Config {
|
||||
println!("Running the service provider!");
|
||||
|
||||
let directory_server = matches
|
||||
.value_of("directory")
|
||||
.unwrap_or("https://directory.nymtech.net")
|
||||
.to_string();
|
||||
|
||||
let mix_host = matches.value_of("mixHost").unwrap();
|
||||
let mix_port = match matches.value_of("mixPort").unwrap_or("8085").parse::<u16>() {
|
||||
Ok(n) => n,
|
||||
Err(err) => panic!("Invalid mix host port value provided - {:?}", err),
|
||||
};
|
||||
|
||||
let client_host = matches.value_of("clientHost").unwrap();
|
||||
let client_port = match matches
|
||||
.value_of("clientPort")
|
||||
.unwrap_or("9000")
|
||||
.parse::<u16>()
|
||||
{
|
||||
Ok(n) => n,
|
||||
Err(err) => panic!("Invalid client port value provided - {:?}", err),
|
||||
};
|
||||
|
||||
let key_pair = KeyPair::new(); // TODO: persist this so keypairs don't change every restart
|
||||
let store_dir = PathBuf::from(
|
||||
matches
|
||||
.value_of("storeDir")
|
||||
.unwrap_or("/tmp/nym-provider/inboxes"),
|
||||
);
|
||||
let registered_client_ledger_dir = PathBuf::from(
|
||||
matches
|
||||
.value_of("registeredLedger")
|
||||
.unwrap_or("/tmp/nym-provider/registered_clients"),
|
||||
);
|
||||
|
||||
println!("The value of mix_host is: {:?}", mix_host);
|
||||
println!("The value of mix_port is: {:?}", mix_port);
|
||||
println!("The value of client_host is: {:?}", client_host);
|
||||
println!("The value of client_port is: {:?}", client_port);
|
||||
println!("The value of key is: {:?}", key_pair.private.clone());
|
||||
println!("The value of store_dir is: {:?}", store_dir);
|
||||
println!(
|
||||
"The value of registered_client_ledger_dir is: {:?}",
|
||||
registered_client_ledger_dir
|
||||
);
|
||||
|
||||
let mix_socket_address = (mix_host, mix_port)
|
||||
.to_socket_addrs()
|
||||
.expect("Failed to combine host and port")
|
||||
.next()
|
||||
.expect("Failed to extract the socket address from the iterator");
|
||||
|
||||
let client_socket_address = (client_host, client_port)
|
||||
.to_socket_addrs()
|
||||
.expect("Failed to combine host and port")
|
||||
.next()
|
||||
.expect("Failed to extract the socket address from the iterator");
|
||||
|
||||
println!(
|
||||
"The full combined mix socket address is {}",
|
||||
mix_socket_address
|
||||
);
|
||||
println!(
|
||||
"The full combined client socket address is {}",
|
||||
client_socket_address
|
||||
);
|
||||
|
||||
provider::Config {
|
||||
mix_socket_address,
|
||||
directory_server,
|
||||
public_key: key_pair.public,
|
||||
client_socket_address,
|
||||
secret_key: key_pair.private,
|
||||
store_dir: PathBuf::from(store_dir),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod built_info {
|
||||
// The file has been placed there by the build script.
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
}
|
||||
|
||||
fn execute(matches: ArgMatches) -> Result<(), String> {
|
||||
match matches.subcommand() {
|
||||
("run", Some(m)) => Ok(run(m)),
|
||||
_ => Err(usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> String {
|
||||
banner() + "usage: --help to see available options.\n\n"
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
format!(
|
||||
r#"
|
||||
|
||||
_ __ _ _ _ __ ___
|
||||
| '_ \| | | | '_ \ _ \
|
||||
| | | | |_| | | | | | |
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(store-and-forward provider - version {:})
|
||||
|
||||
"#,
|
||||
built_info::PKG_VERSION
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
use crate::provider::storage::{ClientStorage, StoreError};
|
||||
use crate::provider::ClientLedger;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use sfw_provider_requests::requests::{
|
||||
ProviderRequestError, ProviderRequests, PullRequest, RegisterRequest,
|
||||
};
|
||||
use sfw_provider_requests::responses::{ProviderResponse, PullResponse, RegisterResponse};
|
||||
use sfw_provider_requests::AuthToken;
|
||||
use sha2::Sha256;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientProcessingError {
|
||||
ClientDoesntExistError,
|
||||
StoreError,
|
||||
InvalidRequest,
|
||||
WrongToken,
|
||||
IOError,
|
||||
}
|
||||
|
||||
impl From<ProviderRequestError> for ClientProcessingError {
|
||||
fn from(_: ProviderRequestError) -> Self {
|
||||
use ClientProcessingError::*;
|
||||
|
||||
InvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoreError> for ClientProcessingError {
|
||||
fn from(e: StoreError) -> Self {
|
||||
match e {
|
||||
StoreError::ClientDoesntExistError => ClientProcessingError::ClientDoesntExistError,
|
||||
_ => ClientProcessingError::StoreError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ClientProcessingError {
|
||||
fn from(_: io::Error) -> Self {
|
||||
use ClientProcessingError::*;
|
||||
|
||||
IOError
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ClientProcessingData {
|
||||
store_dir: PathBuf,
|
||||
registered_clients_ledger: Arc<FMutex<ClientLedger>>,
|
||||
secret_key: Scalar,
|
||||
}
|
||||
|
||||
impl ClientProcessingData {
|
||||
pub(crate) fn new(
|
||||
store_dir: PathBuf,
|
||||
registered_clients_ledger: Arc<FMutex<ClientLedger>>,
|
||||
secret_key: Scalar,
|
||||
) -> Self {
|
||||
ClientProcessingData {
|
||||
store_dir,
|
||||
registered_clients_ledger,
|
||||
secret_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_arc(self) -> Arc<Self> {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ClientRequestProcessor;
|
||||
|
||||
impl ClientRequestProcessor {
|
||||
pub(crate) async fn process_client_request(
|
||||
data: &[u8],
|
||||
processing_data: Arc<ClientProcessingData>,
|
||||
) -> Result<Vec<u8>, ClientProcessingError> {
|
||||
let client_request = ProviderRequests::from_bytes(&data)?;
|
||||
println!("Received the following request: {:?}", client_request);
|
||||
match client_request {
|
||||
ProviderRequests::Register(req) => Ok(ClientRequestProcessor::register_new_client(
|
||||
req,
|
||||
processing_data,
|
||||
)
|
||||
.await?
|
||||
.to_bytes()),
|
||||
ProviderRequests::PullMessages(req) => Ok(
|
||||
ClientRequestProcessor::process_pull_messages_request(req, processing_data)
|
||||
.await?
|
||||
.to_bytes(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_pull_messages_request(
|
||||
req: PullRequest,
|
||||
processing_data: Arc<ClientProcessingData>,
|
||||
) -> Result<PullResponse, ClientProcessingError> {
|
||||
// TODO: this lock is completely unnecessary as we're only reading the data.
|
||||
// Wait for https://github.com/nymtech/nym-sfw-provider/issues/19 to resolve.
|
||||
let unlocked_ledger = processing_data.registered_clients_ledger.lock().await;
|
||||
|
||||
println!("Processing pull!");
|
||||
if unlocked_ledger.has_token(req.auth_token) {
|
||||
// drop the mutex so that we could do IO without blocking others wanting to get the lock
|
||||
drop(unlocked_ledger);
|
||||
let retrieved_messages = ClientStorage::retrieve_client_files(
|
||||
req.destination_address,
|
||||
processing_data.store_dir.as_path(),
|
||||
)?;
|
||||
Ok(PullResponse::new(retrieved_messages))
|
||||
} else {
|
||||
Err(ClientProcessingError::WrongToken)
|
||||
}
|
||||
}
|
||||
|
||||
async fn register_new_client(
|
||||
req: RegisterRequest,
|
||||
processing_data: Arc<ClientProcessingData>,
|
||||
) -> Result<RegisterResponse, ClientProcessingError> {
|
||||
println!("Processing register new client request!");
|
||||
let mut unlocked_ledger = processing_data.registered_clients_ledger.lock().await;
|
||||
|
||||
let auth_token = ClientRequestProcessor::generate_new_auth_token(
|
||||
req.destination_address.to_vec(),
|
||||
processing_data.secret_key,
|
||||
);
|
||||
if !unlocked_ledger.has_token(auth_token) {
|
||||
unlocked_ledger.insert_token(auth_token, req.destination_address);
|
||||
ClientRequestProcessor::create_storage_dir(
|
||||
req.destination_address,
|
||||
processing_data.store_dir.as_path(),
|
||||
)?;
|
||||
}
|
||||
Ok(RegisterResponse::new(auth_token))
|
||||
}
|
||||
|
||||
fn create_storage_dir(
|
||||
client_address: sphinx::route::DestinationAddressBytes,
|
||||
store_dir: &Path,
|
||||
) -> io::Result<()> {
|
||||
let client_dir_name = hex::encode(client_address);
|
||||
let full_store_dir = store_dir.join(client_dir_name);
|
||||
std::fs::create_dir_all(full_store_dir)
|
||||
}
|
||||
|
||||
fn generate_new_auth_token(data: Vec<u8>, key: Scalar) -> AuthToken {
|
||||
let mut auth_token_raw =
|
||||
HmacSha256::new_varkey(&key.to_bytes()).expect("HMAC can take key of any size");
|
||||
auth_token_raw.input(&data);
|
||||
let mut auth_token = [0u8; 32];
|
||||
auth_token.copy_from_slice(&auth_token_raw.result().code().to_vec());
|
||||
auth_token
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod register_new_client {
|
||||
// use super::*;
|
||||
|
||||
// TODO: those tests require being called in async context. we need to research how to test this stuff...
|
||||
// #[test]
|
||||
// fn registers_new_auth_token_for_each_new_client() {
|
||||
// let req1 = RegisterRequest {
|
||||
// destination_address: [1u8; 32],
|
||||
// };
|
||||
// let registered_client_ledger = ClientLedger::new();
|
||||
// let store_dir = PathBuf::from("./foo/");
|
||||
// let key = Scalar::from_bytes_mod_order([1u8; 32]);
|
||||
// let client_processing_data = ClientProcessingData::new(store_dir, registered_client_ledger, key).add_arc_futures_mutex();
|
||||
//
|
||||
//
|
||||
// // need to do async....
|
||||
// client_processing_data.lock().await;
|
||||
// assert_eq!(0, registered_client_ledger.0.len());
|
||||
// ClientRequestProcessor::register_new_client(
|
||||
// req1,
|
||||
// client_processing_data.clone(),
|
||||
// );
|
||||
//
|
||||
// assert_eq!(1, registered_client_ledger.0.len());
|
||||
//
|
||||
// let req2 = RegisterRequest {
|
||||
// destination_address: [2u8; 32],
|
||||
// };
|
||||
// ClientRequestProcessor::register_new_client(
|
||||
// req2,
|
||||
// client_processing_data,
|
||||
// );
|
||||
// assert_eq!(2, registered_client_ledger.0.len());
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn registers_given_token_only_once() {
|
||||
// let req1 = RegisterRequest {
|
||||
// destination_address: [1u8; 32],
|
||||
// };
|
||||
// let registered_client_ledger = ClientLedger::new();
|
||||
// let store_dir = PathBuf::from("./foo/");
|
||||
// let key = Scalar::from_bytes_mod_order([1u8; 32]);
|
||||
// let client_processing_data = ClientProcessingData::new(store_dir, registered_client_ledger, key).add_arc_futures_mutex();
|
||||
//
|
||||
// ClientRequestProcessor::register_new_client(
|
||||
// req1,
|
||||
// client_processing_data.clone(),
|
||||
// );
|
||||
// let req2 = RegisterRequest {
|
||||
// destination_address: [1u8; 32],
|
||||
// };
|
||||
// ClientRequestProcessor::register_new_client(
|
||||
// req2,
|
||||
// client_processing_data.clone(),
|
||||
// );
|
||||
//
|
||||
// client_processing_data.lock().await;
|
||||
//
|
||||
// assert_eq!(1, registered_client_ledger.0.len())
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod create_storage_dir {
|
||||
use super::*;
|
||||
use sphinx::route::DestinationAddressBytes;
|
||||
|
||||
#[test]
|
||||
fn it_creates_a_correct_storage_directory() {
|
||||
let client_address: DestinationAddressBytes = [1u8; 32];
|
||||
let store_dir = Path::new("./foo/");
|
||||
ClientRequestProcessor::create_storage_dir(client_address, store_dir).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod generating_new_auth_token {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn for_the_same_input_generates_the_same_auth_token() {
|
||||
let data1 = vec![1u8; 55];
|
||||
let data2 = vec![1u8; 55];
|
||||
let key = Scalar::from_bytes_mod_order([1u8; 32]);
|
||||
let token1 = ClientRequestProcessor::generate_new_auth_token(data1, key);
|
||||
let token2 = ClientRequestProcessor::generate_new_auth_token(data2, key);
|
||||
assert_eq!(token1, token2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_different_inputs_generates_different_auth_tokens() {
|
||||
let data1 = vec![1u8; 55];
|
||||
let data2 = vec![2u8; 55];
|
||||
let key = Scalar::from_bytes_mod_order([1u8; 32]);
|
||||
let token1 = ClientRequestProcessor::generate_new_auth_token(data1, key);
|
||||
let token2 = ClientRequestProcessor::generate_new_auth_token(data2, key);
|
||||
assert_ne!(token1, token2);
|
||||
|
||||
let data1 = vec![1u8; 50];
|
||||
let data2 = vec![2u8; 55];
|
||||
let key = Scalar::from_bytes_mod_order([1u8; 32]);
|
||||
let token1 = ClientRequestProcessor::generate_new_auth_token(data1, key);
|
||||
let token2 = ClientRequestProcessor::generate_new_auth_token(data2, key);
|
||||
assert_ne!(token1, token2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::provider::storage::StoreData;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use sphinx::{ProcessedPacket, SphinxPacket};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
// TODO: this will probably need to be moved elsewhere I imagine
|
||||
// DUPLICATE WITH MIXNODE CODE!!!
|
||||
#[derive(Debug)]
|
||||
pub enum MixProcessingError {
|
||||
SphinxRecoveryError,
|
||||
ReceivedForwardHopError,
|
||||
InvalidPayload,
|
||||
NonMatchingRecipient,
|
||||
FileIOFailure,
|
||||
}
|
||||
|
||||
impl From<sphinx::ProcessingError> for MixProcessingError {
|
||||
// for time being just have a single error instance for all possible results of sphinx::ProcessingError
|
||||
fn from(_: sphinx::ProcessingError) -> Self {
|
||||
use MixProcessingError::*;
|
||||
|
||||
SphinxRecoveryError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for MixProcessingError {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
use MixProcessingError::*;
|
||||
|
||||
FileIOFailure
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessingData defines all data required to correctly unwrap sphinx packets
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MixProcessingData {
|
||||
secret_key: Scalar,
|
||||
pub(crate) store_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl MixProcessingData {
|
||||
pub(crate) fn new(secret_key: Scalar, store_dir: PathBuf) -> Self {
|
||||
MixProcessingData {
|
||||
secret_key,
|
||||
store_dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_arc_rwlock(self) -> Arc<RwLock<Self>> {
|
||||
Arc::new(RwLock::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MixPacketProcessor(());
|
||||
|
||||
impl MixPacketProcessor {
|
||||
pub fn process_sphinx_data_packet(
|
||||
packet_data: &[u8],
|
||||
processing_data: &RwLock<MixProcessingData>,
|
||||
) -> Result<StoreData, MixProcessingError> {
|
||||
let packet = SphinxPacket::from_bytes(packet_data.to_vec())?;
|
||||
let read_processing_data = processing_data.read().unwrap();
|
||||
let (client_address, client_surb_id, payload) =
|
||||
match packet.process(read_processing_data.secret_key) {
|
||||
ProcessedPacket::ProcessedPacketFinalHop(client_address, surb_id, payload) => {
|
||||
(client_address, surb_id, payload)
|
||||
}
|
||||
_ => return Err(MixProcessingError::ReceivedForwardHopError),
|
||||
};
|
||||
|
||||
// TODO: should provider try to be recovering plaintext? this would potentially make client retrieve messages of non-constant length,
|
||||
// perhaps provider should be re-padding them on retrieval or storing full data?
|
||||
let (payload_destination, message) = payload
|
||||
.try_recover_destination_and_plaintext()
|
||||
.ok_or_else(|| MixProcessingError::InvalidPayload)?;
|
||||
if client_address != payload_destination {
|
||||
return Err(MixProcessingError::NonMatchingRecipient);
|
||||
}
|
||||
|
||||
Ok(StoreData::new(client_address, client_surb_id, message))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
use crate::provider::client_handling::{ClientProcessingData, ClientRequestProcessor};
|
||||
use crate::provider::mix_handling::{MixPacketProcessor, MixProcessingData};
|
||||
use crate::provider::storage::ClientStorage;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use futures::io::Error;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
use nym_client::clients::directory::presence::MixProviderClient;
|
||||
use sfw_provider_requests::AuthToken;
|
||||
use sphinx::route::DestinationAddressBytes;
|
||||
use std::collections::HashMap;
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
mod client_handling;
|
||||
mod mix_handling;
|
||||
pub mod presence;
|
||||
mod storage;
|
||||
|
||||
// TODO: if we ever create config file, this should go there
|
||||
const STORED_MESSAGE_FILENAME_LENGTH: usize = 16;
|
||||
const MESSAGE_RETRIEVAL_LIMIT: usize = 5;
|
||||
|
||||
pub struct Config {
|
||||
pub client_socket_address: SocketAddr,
|
||||
pub directory_server: String,
|
||||
pub mix_socket_address: SocketAddr,
|
||||
pub public_key: MontgomeryPoint,
|
||||
pub secret_key: Scalar,
|
||||
pub store_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn public_key_string(public_key: MontgomeryPoint) -> String {
|
||||
let key_bytes = public_key.to_bytes().to_vec();
|
||||
base64::encode_config(&key_bytes, base64::URL_SAFE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderError {
|
||||
TcpListenerBindingError,
|
||||
TcpListenerConnectionError,
|
||||
TcpListenerUnexpectedEof,
|
||||
|
||||
TcpListenerUnknownError,
|
||||
}
|
||||
|
||||
impl From<io::Error> for ProviderError {
|
||||
fn from(err: Error) -> Self {
|
||||
use ProviderError::*;
|
||||
match err.kind() {
|
||||
io::ErrorKind::ConnectionRefused => TcpListenerConnectionError,
|
||||
io::ErrorKind::ConnectionReset => TcpListenerConnectionError,
|
||||
io::ErrorKind::ConnectionAborted => TcpListenerConnectionError,
|
||||
io::ErrorKind::NotConnected => TcpListenerConnectionError,
|
||||
|
||||
io::ErrorKind::AddrInUse => TcpListenerBindingError,
|
||||
io::ErrorKind::AddrNotAvailable => TcpListenerBindingError,
|
||||
io::ErrorKind::UnexpectedEof => TcpListenerUnexpectedEof,
|
||||
_ => TcpListenerUnknownError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientLedger(HashMap<AuthToken, DestinationAddressBytes>);
|
||||
|
||||
impl ClientLedger {
|
||||
fn new() -> Self {
|
||||
ClientLedger(HashMap::new())
|
||||
}
|
||||
|
||||
fn add_arc_futures_mutex(self) -> Arc<FMutex<Self>> {
|
||||
Arc::new(FMutex::new(self))
|
||||
}
|
||||
|
||||
fn has_token(&self, auth_token: AuthToken) -> bool {
|
||||
return self.0.contains_key(&auth_token);
|
||||
}
|
||||
|
||||
fn insert_token(
|
||||
&mut self,
|
||||
auth_token: AuthToken,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Option<DestinationAddressBytes> {
|
||||
self.0.insert(auth_token, client_address)
|
||||
}
|
||||
|
||||
fn current_clients(&self) -> Vec<MixProviderClient> {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|(_, &v)| Config::public_key_string(MontgomeryPoint(v)))
|
||||
.map(|pub_key| MixProviderClient { pub_key })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn load(_file: PathBuf) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceProvider {
|
||||
directory_server: String,
|
||||
mix_network_address: SocketAddr,
|
||||
client_network_address: SocketAddr,
|
||||
secret_key: Scalar,
|
||||
public_key: MontgomeryPoint,
|
||||
store_dir: PathBuf,
|
||||
registered_clients_ledger: ClientLedger,
|
||||
}
|
||||
|
||||
impl ServiceProvider {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
ServiceProvider {
|
||||
mix_network_address: config.mix_socket_address,
|
||||
client_network_address: config.client_socket_address,
|
||||
secret_key: config.secret_key,
|
||||
public_key: config.public_key,
|
||||
store_dir: PathBuf::from(config.store_dir.clone()),
|
||||
// TODO: load initial ledger from file
|
||||
registered_clients_ledger: ClientLedger::new(),
|
||||
directory_server: config.directory_server.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_mixnet_socket_connection(
|
||||
mut socket: tokio::net::TcpStream,
|
||||
processing_data: Arc<RwLock<MixProcessingData>>,
|
||||
) {
|
||||
let mut buf = [0u8; sphinx::PACKET_SIZE];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => {
|
||||
println!("Remote connection closed.");
|
||||
return;
|
||||
}
|
||||
Ok(_) => {
|
||||
let store_data = match MixPacketProcessor::process_sphinx_data_packet(
|
||||
buf.as_ref(),
|
||||
processing_data.as_ref(),
|
||||
) {
|
||||
Ok(sd) => sd,
|
||||
Err(e) => {
|
||||
eprintln!("failed to process sphinx packet; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
ClientStorage::store_processed_data(
|
||||
store_data,
|
||||
processing_data.read().unwrap().store_dir.as_path(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to store processed sphinx message; err = {:?}", e);
|
||||
return;
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the some data back
|
||||
if let Err(e) = socket.write_all(b"foomp").await {
|
||||
eprintln!("failed to write reply to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_response(mut socket: tokio::net::TcpStream, data: &[u8]) {
|
||||
if let Err(e) = socket.write_all(data).await {
|
||||
eprintln!("failed to write reply to socket; err = {:?}", e)
|
||||
}
|
||||
if let Err(e) = socket.shutdown(Shutdown::Write) {
|
||||
eprintln!("failed to close write part of the socket; err = {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: FIGURE OUT HOW TO SET READ_DEADLINES IN TOKIO
|
||||
async fn process_client_socket_connection(
|
||||
mut socket: tokio::net::TcpStream,
|
||||
processing_data: Arc<ClientProcessingData>,
|
||||
) {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// TODO: restore the for loop once we go back to persistent tcp socket connection
|
||||
let response = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => {
|
||||
println!("Remote connection closed.");
|
||||
Err(())
|
||||
}
|
||||
Ok(n) => {
|
||||
match ClientRequestProcessor::process_client_request(
|
||||
buf[..n].as_ref(),
|
||||
processing_data,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
eprintln!("failed to process client request; err = {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
Ok(res) => Ok(res),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
Err(())
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = socket.shutdown(Shutdown::Read) {
|
||||
eprintln!("failed to close read part of the socket; err = {:?}", e)
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(res) => {
|
||||
println!("should send this response! {:?}", res);
|
||||
ServiceProvider::send_response(socket, &res).await;
|
||||
}
|
||||
_ => {
|
||||
println!("we failed...");
|
||||
ServiceProvider::send_response(socket, b"bad foomp").await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_mixnet_listening(
|
||||
address: SocketAddr,
|
||||
secret_key: Scalar,
|
||||
store_dir: PathBuf,
|
||||
) -> Result<(), ProviderError> {
|
||||
let mut listener = tokio::net::TcpListener::bind(address).await?;
|
||||
let processing_data = MixProcessingData::new(secret_key, store_dir).add_arc_rwlock();
|
||||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await?;
|
||||
// do note that the underlying data is NOT copied here; arc is incremented and lock is shared
|
||||
// (if I understand it all correctly)
|
||||
let thread_processing_data = processing_data.clone();
|
||||
tokio::spawn(async move {
|
||||
ServiceProvider::process_mixnet_socket_connection(socket, thread_processing_data)
|
||||
.await
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_client_listening(
|
||||
address: SocketAddr,
|
||||
store_dir: PathBuf,
|
||||
client_ledger: Arc<FMutex<ClientLedger>>,
|
||||
secret_key: Scalar,
|
||||
) -> Result<(), ProviderError> {
|
||||
let mut listener = tokio::net::TcpListener::bind(address).await?;
|
||||
let processing_data =
|
||||
ClientProcessingData::new(store_dir, client_ledger, secret_key).add_arc();
|
||||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await?;
|
||||
// do note that the underlying data is NOT copied here; arc is incremented and lock is shared
|
||||
// (if I understand it all correctly)
|
||||
let thread_processing_data = processing_data.clone();
|
||||
tokio::spawn(async move {
|
||||
ServiceProvider::process_client_socket_connection(socket, thread_processing_data)
|
||||
.await
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this now consumes the provider
|
||||
pub fn start(self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create the runtime, probably later move it to Provider struct itself?
|
||||
// TODO: figure out the difference between Runtime and Handle
|
||||
let mut rt = Runtime::new()?;
|
||||
// let mut h = rt.handle();
|
||||
|
||||
let initial_client_ledger = self.registered_clients_ledger;
|
||||
let thread_shareable_ledger = initial_client_ledger.add_arc_futures_mutex();
|
||||
|
||||
let presence_notifier = presence::Notifier::new(
|
||||
self.directory_server,
|
||||
self.mix_network_address.clone(),
|
||||
self.public_key,
|
||||
thread_shareable_ledger.clone(),
|
||||
);
|
||||
|
||||
let presence_future = rt.spawn(presence_notifier.run());
|
||||
let mix_future = rt.spawn(ServiceProvider::start_mixnet_listening(
|
||||
self.mix_network_address,
|
||||
self.secret_key,
|
||||
self.store_dir.clone(),
|
||||
));
|
||||
let client_future = rt.spawn(ServiceProvider::start_client_listening(
|
||||
self.client_network_address,
|
||||
self.store_dir.clone(),
|
||||
thread_shareable_ledger,
|
||||
self.secret_key,
|
||||
));
|
||||
// Spawn the root task
|
||||
rt.block_on(async {
|
||||
let future_results =
|
||||
futures::future::join3(mix_future, client_future, presence_future).await;
|
||||
assert!(future_results.0.is_ok() && future_results.1.is_ok());
|
||||
});
|
||||
|
||||
// this line in theory should never be reached as the runtime should be permanently blocked on listeners
|
||||
eprintln!("The server went kaput...");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::provider::{ClientLedger, Config};
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
use nym_client::clients::directory;
|
||||
use nym_client::clients::directory::presence::MixProviderPresence;
|
||||
use nym_client::clients::directory::requests::presence_providers_post::PresenceMixProviderPoster;
|
||||
use nym_client::clients::directory::DirectoryClient;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Notifier {
|
||||
pub net_client: directory::Client,
|
||||
client_ledger: Arc<FMutex<ClientLedger>>,
|
||||
host: String,
|
||||
pub_key: String,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(
|
||||
directory_server_address: String,
|
||||
host: SocketAddr,
|
||||
pub_key: MontgomeryPoint,
|
||||
client_ledger: Arc<FMutex<ClientLedger>>,
|
||||
) -> Notifier {
|
||||
let directory_config = directory::Config {
|
||||
base_url: directory_server_address,
|
||||
};
|
||||
let net_client = directory::Client::new(directory_config);
|
||||
|
||||
Notifier {
|
||||
net_client,
|
||||
host: host.to_string(),
|
||||
pub_key: Config::public_key_string(pub_key),
|
||||
client_ledger,
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_presence(&self) -> MixProviderPresence {
|
||||
let unlocked_ledger = self.client_ledger.lock().await;
|
||||
|
||||
MixProviderPresence {
|
||||
host: self.host.clone(),
|
||||
pub_key: self.pub_key.clone(),
|
||||
registered_clients: unlocked_ledger.current_clients(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self, presence: MixProviderPresence) {
|
||||
self.net_client
|
||||
.presence_providers_post
|
||||
.post(&presence)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn run(self) {
|
||||
loop {
|
||||
let presence = self.make_presence().await;
|
||||
self.notify(presence);
|
||||
let delay_duration = Duration::from_secs(5);
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
use crate::provider::{MESSAGE_RETRIEVAL_LIMIT, STORED_MESSAGE_FILENAME_LENGTH};
|
||||
use rand::Rng;
|
||||
use sfw_provider_requests::DUMMY_MESSAGE_CONTENT;
|
||||
use sphinx::route::{DestinationAddressBytes, SURBIdentifier};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub enum StoreError {
|
||||
ClientDoesntExistError,
|
||||
FileIOFailure,
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for StoreError {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
use StoreError::*;
|
||||
|
||||
FileIOFailure
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StoreData {
|
||||
client_address: DestinationAddressBytes,
|
||||
#[allow(dead_code)]
|
||||
client_surb_id: SURBIdentifier,
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StoreData {
|
||||
pub(crate) fn new(
|
||||
client_address: DestinationAddressBytes,
|
||||
client_surb_id: SURBIdentifier,
|
||||
message: Vec<u8>,
|
||||
) -> Self {
|
||||
StoreData {
|
||||
client_address,
|
||||
client_surb_id,
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with database
|
||||
pub struct ClientStorage(());
|
||||
|
||||
// TODO: change it to some generic implementation to inject fs
|
||||
impl ClientStorage {
|
||||
pub(crate) fn generate_random_file_name() -> String {
|
||||
rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(STORED_MESSAGE_FILENAME_LENGTH)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
fn dummy_message() -> Vec<u8> {
|
||||
// TODO: should it be padded to constant length?
|
||||
DUMMY_MESSAGE_CONTENT.to_vec()
|
||||
}
|
||||
|
||||
pub fn store_processed_data(store_data: StoreData, store_dir: &Path) -> io::Result<()> {
|
||||
let client_dir_name = hex::encode(store_data.client_address);
|
||||
let full_store_dir = store_dir.join(client_dir_name);
|
||||
let full_store_path = full_store_dir.join(ClientStorage::generate_random_file_name());
|
||||
println!(
|
||||
"going to store: {:?} in file: {:?}",
|
||||
store_data.message, full_store_path
|
||||
);
|
||||
|
||||
println!(
|
||||
"string message: {:?}",
|
||||
std::str::from_utf8(&store_data.message)
|
||||
);
|
||||
|
||||
// TODO: what to do with surbIDs??
|
||||
|
||||
// we can use normal io here, no need for tokio as it's all happening in one thread per connection
|
||||
let mut file = File::create(full_store_path)?;
|
||||
file.write_all(store_data.message.as_ref())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn retrieve_client_files(
|
||||
client_address: DestinationAddressBytes,
|
||||
store_dir: &Path,
|
||||
) -> Result<Vec<Vec<u8>>, StoreError> {
|
||||
let client_dir_name = hex::encode(client_address);
|
||||
let full_store_dir = store_dir.join(client_dir_name);
|
||||
|
||||
println!("going to lookup: {:?}!", full_store_dir);
|
||||
if !full_store_dir.exists() {
|
||||
return Err(StoreError::ClientDoesntExistError);
|
||||
}
|
||||
|
||||
let msgs: Vec<_> = std::fs::read_dir(full_store_dir)?
|
||||
.map(|entry| entry.unwrap())
|
||||
.filter(|entry| {
|
||||
let is_file = entry.metadata().unwrap().is_file();
|
||||
if !is_file {
|
||||
eprintln!(
|
||||
"potentially corrupted client inbox! - found a non-file - {:?}",
|
||||
entry.path()
|
||||
);
|
||||
}
|
||||
is_file
|
||||
})
|
||||
.map(|entry| {
|
||||
let content = std::fs::read(entry.path()).unwrap();
|
||||
ClientStorage::delete_file(entry.path()).unwrap();
|
||||
content
|
||||
}) // TODO: THIS MAP IS UNSAFE (BOTH FOR READING AND DELETING)!! - in the case where there are multiple requests from the same client being processed in parallel
|
||||
.chain(std::iter::repeat(ClientStorage::dummy_message()))
|
||||
.take(MESSAGE_RETRIEVAL_LIMIT)
|
||||
.collect();
|
||||
|
||||
println!("retrieved the following data: {:?}", msgs);
|
||||
|
||||
Ok(msgs)
|
||||
}
|
||||
|
||||
// TODO: THIS NEEDS A LOCKING MECHANISM!!! (or a db layer on top - basically 'ClientStorage' on steroids)
|
||||
// TODO 2: This should only be called AFTER we sent the reply. Because if client's connection failed after sending request
|
||||
// the messages would be deleted but he wouldn't have received them
|
||||
fn delete_file(path: PathBuf) -> io::Result<()> {
|
||||
println!("Here {:?} will be deleted!", path);
|
||||
std::fs::remove_file(path) // another argument for db layer -> remove_file is NOT guaranteed to immediately get rid of the file
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user