Compare commits
442 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe88e20350 | |||
| aaa8e54c80 | |||
| 766dbee75a | |||
| 2e2c90983a | |||
| 518244f6bf | |||
| 809db67efd | |||
| ac79a0da69 | |||
| 1769fa8a5a | |||
| 4cc7fc309b | |||
| f966df4fb1 | |||
| 781c4c551d | |||
| ac0566aa17 | |||
| 66b2429b2e | |||
| 325670e540 | |||
| 8d84523c32 | |||
| 851082dc6e | |||
| dcb548ee8e | |||
| 330e7d8d29 | |||
| 3ab4b94a8a | |||
| 09e88da1a9 | |||
| 773fbceff7 | |||
| 84e2292ed0 | |||
| 59ea4306b8 | |||
| b1edf54403 | |||
| a0654b9b59 | |||
| fe35bcbe1c | |||
| fcf11a1c36 | |||
| 05db16677d | |||
| 330cfdc259 | |||
| 6cad7bcbef | |||
| bf9463b24a | |||
| d69603f87b | |||
| 173daa7305 | |||
| b9c37f2845 | |||
| 8237f5d885 | |||
| 76432cf253 | |||
| c1c41ebde1 | |||
| 9082e93826 | |||
| 63cebd27cd | |||
| 505a0bd0b0 | |||
| fb6ec68046 | |||
| 5121e2fb0c | |||
| 25c9ee5b3c | |||
| a6014a6ad5 | |||
| ea7a5cf044 | |||
| cef1568e33 | |||
| 315ece38ae | |||
| 6fbc6c6680 | |||
| 7895eb8c38 | |||
| 164ebf47be | |||
| 1c85ba266f | |||
| e34a55c60a | |||
| 9bd4c49be9 | |||
| c1eb553cc0 | |||
| 04f11ecafa | |||
| 2d7fff06fe | |||
| 1d2481e558 | |||
| 19dff546b4 | |||
| 507e910e72 | |||
| 6b68998e93 | |||
| aad70d65b8 | |||
| f670c7f58f | |||
| 3ec3b4a793 | |||
| d92cee59a0 | |||
| 350eeae8da | |||
| 40fee72161 | |||
| 51d802d1c7 | |||
| ee32b97fbb | |||
| ee1a0033ac | |||
| a615dacf64 | |||
| 34de9cc10b | |||
| 03d04d5aa7 | |||
| 05ca3e147f | |||
| 0b1bae6ea1 | |||
| c056485471 | |||
| d152d0fd12 | |||
| 5bc5974bdb | |||
| 5e870e4c8a | |||
| 8178d505c3 | |||
| 78a52df0ec | |||
| 0ecab55959 | |||
| b3f056b933 | |||
| 520973b578 | |||
| d4ab7f96f1 | |||
| 911ab8ff4c | |||
| 9596060972 | |||
| 4b7971899c | |||
| 6eeedf858d | |||
| 7045d76039 | |||
| 90266175a1 | |||
| c810d0d957 | |||
| a4990f1944 | |||
| 52054f8b46 | |||
| 489c806292 | |||
| db69105813 | |||
| d39e532e4b | |||
| 25575b4598 | |||
| 769598d40a | |||
| 227f69befb | |||
| 9d760a515a | |||
| 26663100b4 | |||
| 7f6ec5ecdf | |||
| a58c3dbb25 | |||
| be0616b524 | |||
| 92390f9ad7 | |||
| 3e9221f6af | |||
| 1ad651fe72 | |||
| e4501852d5 | |||
| 402b226080 | |||
| c772b9a4df | |||
| 95cd4123b5 | |||
| 4164491bc2 | |||
| 0492a21bcf | |||
| 50d54d44c7 | |||
| 1cc1d2ddf9 | |||
| 0dda60cd81 | |||
| 114c927028 | |||
| e18ed7e0fd | |||
| 3e533cdee2 | |||
| 6e3b2c36fe | |||
| 73028f85a9 | |||
| d0d894a309 | |||
| db92f898d3 | |||
| 740a2fb2ff | |||
| 84e34e5490 | |||
| 2a05d77d94 | |||
| 97d7f6d7cb | |||
| 4f23d7e58b | |||
| ccf1e6fe1a | |||
| 7068dc7089 | |||
| 7f6f366e51 | |||
| 4d997ef03b | |||
| 65042e7210 | |||
| 89e17b0ffa | |||
| 6e52afef79 | |||
| a37d9aedc4 | |||
| fbd1f05eb9 | |||
| 3efbea8263 | |||
| 582c8dbc98 | |||
| 1e0d84fb8b | |||
| ca2faf0095 | |||
| e0c110ee55 | |||
| db2f803023 | |||
| d7f444c659 | |||
| 3b7f5c7c68 | |||
| 3072d56417 | |||
| 59bff7a0d5 | |||
| fadd781fc3 | |||
| b5518babac | |||
| cd28f8051b | |||
| 4966d8ecef | |||
| 3b7d22e675 | |||
| 6ceec8e737 | |||
| 077c59f761 | |||
| fd9829bedd | |||
| 1d79c43782 | |||
| c029117674 | |||
| ac78e160df | |||
| eb75362307 | |||
| 6c3e7b6256 | |||
| 7a3d61374f | |||
| ec78c45ea0 | |||
| 1337f93c33 | |||
| 1f3875f39c | |||
| f735befd52 | |||
| 2a9e21538f | |||
| 8ac1e58ef6 | |||
| c97b1b5edf | |||
| e773b251b9 | |||
| 9c8347aa72 | |||
| fcaa23fd77 | |||
| 5fa3eb35e4 | |||
| 67a900701e | |||
| 8314d647d9 | |||
| b88ca3ed91 | |||
| ee43e82235 | |||
| ce866bd987 | |||
| 95f103b4fd | |||
| 5ad6db9232 | |||
| c1cd6557eb | |||
| 8c3cbe6646 | |||
| add94ed70a | |||
| 6fe9dc899b | |||
| 6fedba093b | |||
| fb15c636eb | |||
| 043403b1e0 | |||
| 13eda7f68f | |||
| 9b94b1d72b | |||
| 07db413f2c | |||
| db67744b19 | |||
| 1292cdb145 | |||
| 7fa42d16eb | |||
| a673f9f206 | |||
| 8706f7ed06 | |||
| 883ce22c5b | |||
| 9ba1ea203b | |||
| 2cef804a34 | |||
| 46ce9eaff7 | |||
| d4cba72118 | |||
| 708d6d5aed | |||
| ff7af13080 | |||
| f325ea6b92 | |||
| d42a75cea9 | |||
| f4d4556ced | |||
| d79d57fcaa | |||
| 9712322597 | |||
| e3038b2b57 | |||
| ba1ebd18e0 | |||
| 9c70c64825 | |||
| ddc988a10d | |||
| aa4d759c38 | |||
| d94dcea7ee | |||
| 3d98ed7b6c | |||
| 18ac7e90ed | |||
| b04b62ed27 | |||
| 4066b4db42 | |||
| 037f05f323 | |||
| 3f358a6929 | |||
| e79a63588a | |||
| 60f15d57bd | |||
| e422ce0a13 | |||
| 598d68a822 | |||
| f3900f3659 | |||
| fa2d881442 | |||
| 435b7ae7e9 | |||
| 950a1ec0f8 | |||
| b483e15d5d | |||
| 4d67d11a39 | |||
| 1e719a8861 | |||
| d856911a1d | |||
| d50666df0c | |||
| f6bf239cc2 | |||
| bd5c1dd357 | |||
| 8a558746fe | |||
| df23148ccf | |||
| cf62a7ba12 | |||
| bfad7d1087 | |||
| 71d23f44c2 | |||
| 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 |
@@ -0,0 +1,2 @@
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
+6
-2
@@ -1,3 +1,7 @@
|
||||
/target
|
||||
/targetString
|
||||
**/*.rs.bk
|
||||
/.idea/
|
||||
/*/target
|
||||
/test_inbox
|
||||
.idea
|
||||
target
|
||||
.env
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
jobs:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
fast_finish: true
|
||||
@@ -1,12 +0,0 @@
|
||||
# nym-client Changelog
|
||||
|
||||
* 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
|
||||
* client version number is now shown at node start
|
||||
* directory server location is now shown at node start
|
||||
* decrease default delays
|
||||
|
||||
## 0.1.0 - Initial Release
|
||||
|
||||
* The bare minimum set of features required by a Nym Client
|
||||
Generated
+1119
-874
File diff suppressed because it is too large
Load Diff
+16
-38
@@ -1,39 +1,17 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-client"
|
||||
version = "0.2.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
name = "nym_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
# 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"
|
||||
dirs = "2.0.2"
|
||||
futures = "0.3.1"
|
||||
hex = "0.4.0"
|
||||
pem = "0.7.0"
|
||||
rand = "0.7.2"
|
||||
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" }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tungstenite = "0.9.2"
|
||||
|
||||
# putting this explicitly below everything and most likely, the next time we look into it, it will already have a proper release
|
||||
tokio-tungstenite = { git = "https://github.com/dbcfd/tokio-tungstenite", rev="6dc2018cbfe8fe7ddd75ff977343086503135b38" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "0.22.0"
|
||||
members = [
|
||||
"common/clients/directory-client",
|
||||
"common/clients/mix-client",
|
||||
"common/clients/provider-client",
|
||||
"common/clients/validator-client",
|
||||
"common/addressing",
|
||||
"common/crypto",
|
||||
"common/healthcheck",
|
||||
"common/topology",
|
||||
"mixnode",
|
||||
"nym-client",
|
||||
"sfw-provider",
|
||||
"sfw-provider/sfw-provider-requests",
|
||||
"validator",
|
||||
]
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
# 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.
|
||||
|
||||
[](https://travis-ci.com/nymtech/nym)
|
||||
|
||||
### Building
|
||||
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs/mixnet/installation/).
|
||||
|
||||
### Developing
|
||||
|
||||
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "addressing"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "directory-client"
|
||||
version = "0.1.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]
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
|
||||
## internal
|
||||
topology = {path = "../../topology"}
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "0.22.0"
|
||||
@@ -1,22 +1,16 @@
|
||||
use crate::clients::directory::requests::health_check_get::{
|
||||
HealthCheckRequester, Request as HealthCheckRequest,
|
||||
};
|
||||
use crate::clients::directory::requests::metrics_mixes_get::{
|
||||
MetricsMixRequester, Request as MetricsMixRequest,
|
||||
};
|
||||
use crate::clients::directory::requests::metrics_mixes_post::{
|
||||
MetricsMixPoster, Request as MetricsMixPost,
|
||||
};
|
||||
use crate::clients::directory::requests::presence_coconodes_post::{
|
||||
use crate::requests::health_check_get::{HealthCheckRequester, Request as HealthCheckRequest};
|
||||
use crate::requests::metrics_mixes_get::{MetricsMixRequester, Request as MetricsMixRequest};
|
||||
use crate::requests::metrics_mixes_post::{MetricsMixPoster, Request as MetricsMixPost};
|
||||
use crate::requests::presence_coconodes_post::{
|
||||
PresenceCocoNodesPoster, Request as PresenceCocoNodesPost,
|
||||
};
|
||||
use crate::clients::directory::requests::presence_mixnodes_post::{
|
||||
use crate::requests::presence_mixnodes_post::{
|
||||
PresenceMixNodesPoster, Request as PresenceMixNodesPost,
|
||||
};
|
||||
use crate::clients::directory::requests::presence_providers_post::{
|
||||
use crate::requests::presence_providers_post::{
|
||||
PresenceMixProviderPoster, Request as PresenceProvidersPost,
|
||||
};
|
||||
use crate::clients::directory::requests::presence_topology_get::{
|
||||
use crate::requests::presence_topology_get::{
|
||||
PresenceTopologyGetRequester, Request as PresenceTopologyRequest,
|
||||
};
|
||||
|
||||
@@ -28,6 +22,12 @@ pub struct Config {
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(base_url: String) -> Self {
|
||||
Config { base_url }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DirectoryClient {
|
||||
fn new(config: Config) -> Self;
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
use crate::requests::presence_topology_get::PresenceTopologyGetRequester;
|
||||
use crate::{Client, Config, DirectoryClient};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use topology::{CocoNode, MixNode, MixProviderNode, NymTopology};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CocoPresence {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::CocoNode> for CocoPresence {
|
||||
fn into(self) -> topology::CocoNode {
|
||||
topology::CocoNode {
|
||||
host: self.host,
|
||||
pub_key: self.pub_key,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::CocoNode> for CocoPresence {
|
||||
fn from(cn: CocoNode) -> Self {
|
||||
CocoPresence {
|
||||
host: cn.host,
|
||||
pub_key: cn.pub_key,
|
||||
last_seen: cn.last_seen,
|
||||
version: cn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixNodePresence {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl TryInto<topology::MixNode> for MixNodePresence {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_into(self) -> Result<MixNode, Self::Error> {
|
||||
let resolved_hostname = self.host.to_socket_addrs()?.next();
|
||||
if resolved_hostname.is_none() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"no valid socket address",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(topology::MixNode {
|
||||
host: resolved_hostname.unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
layer: self.layer,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::MixNode> for MixNodePresence {
|
||||
fn from(mn: MixNode) -> Self {
|
||||
MixNodePresence {
|
||||
host: mn.host.to_string(),
|
||||
pub_key: mn.pub_key,
|
||||
layer: mn.layer,
|
||||
last_seen: mn.last_seen,
|
||||
version: mn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderPresence {
|
||||
pub client_listener: String,
|
||||
pub mixnet_listener: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<MixProviderClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::MixProviderNode> for MixProviderPresence {
|
||||
fn into(self) -> topology::MixProviderNode {
|
||||
topology::MixProviderNode {
|
||||
client_listener: self.client_listener.parse().unwrap(),
|
||||
mixnet_listener: self.mixnet_listener.parse().unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
registered_clients: self
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::MixProviderNode> for MixProviderPresence {
|
||||
fn from(mpn: MixProviderNode) -> Self {
|
||||
MixProviderPresence {
|
||||
client_listener: mpn.client_listener.to_string(),
|
||||
mixnet_listener: mpn.mixnet_listener.to_string(),
|
||||
pub_key: mpn.pub_key,
|
||||
registered_clients: mpn
|
||||
.registered_clients
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect(),
|
||||
last_seen: mpn.last_seen,
|
||||
version: mpn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
impl Into<topology::MixProviderClient> for MixProviderClient {
|
||||
fn into(self) -> topology::MixProviderClient {
|
||||
topology::MixProviderClient {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::MixProviderClient> for MixProviderClient {
|
||||
fn from(mpc: topology::MixProviderClient) -> Self {
|
||||
MixProviderClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Topology shows us the current state of the overall Nym network
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Topology {
|
||||
pub coco_nodes: Vec<CocoPresence>,
|
||||
pub mix_nodes: Vec<MixNodePresence>,
|
||||
pub mix_provider_nodes: Vec<MixProviderPresence>,
|
||||
}
|
||||
|
||||
impl NymTopology for Topology {
|
||||
fn new(directory_server: String) -> Self {
|
||||
println!("Using directory server: {:?}", directory_server);
|
||||
let directory_config = Config {
|
||||
base_url: directory_server,
|
||||
};
|
||||
let directory = Client::new(directory_config);
|
||||
|
||||
let topology = directory
|
||||
.presence_topology
|
||||
.get()
|
||||
.expect("Failed to retrieve network topology.");
|
||||
topology
|
||||
}
|
||||
|
||||
fn new_from_nodes(
|
||||
mix_nodes: Vec<MixNode>,
|
||||
mix_provider_nodes: Vec<MixProviderNode>,
|
||||
coco_nodes: Vec<CocoNode>,
|
||||
) -> Self {
|
||||
Topology {
|
||||
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
|
||||
mix_nodes: mix_nodes.into_iter().map(|node| node.into()).collect(),
|
||||
mix_provider_nodes: mix_provider_nodes
|
||||
.into_iter()
|
||||
.map(|node| node.into())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mix_nodes(&self) -> Vec<topology::MixNode> {
|
||||
self.mix_nodes
|
||||
.iter()
|
||||
.filter_map(|x| x.clone().try_into().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_mix_provider_nodes(&self) -> Vec<topology::MixProviderNode> {
|
||||
self.mix_provider_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_coco_nodes(&self) -> Vec<topology::CocoNode> {
|
||||
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod converting_mixnode_presence_into_topology_mixnode {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_returns_error_on_unresolvable_hostname() {
|
||||
let unresolvable_hostname = "foomp.foomp.foomp:1234";
|
||||
|
||||
let mix_presence = MixNodePresence {
|
||||
host: unresolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<topology::MixNode, io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_returns_resolved_ip_on_resolvable_hostname() {
|
||||
let resolvable_hostname = "nymtech.net:1234";
|
||||
|
||||
let mix_presence = MixNodePresence {
|
||||
host: resolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<topology::MixNode, io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::metrics::PersistedMixMetric;
|
||||
use crate::metrics::PersistedMixMetric;
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::metrics::MixMetric;
|
||||
use crate::metrics::MixMetric;
|
||||
use reqwest::Response;
|
||||
|
||||
pub struct Request {
|
||||
@@ -69,7 +69,7 @@ mod metrics_get_request {
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::clients::directory::metrics::MixMetric;
|
||||
use crate::metrics::MixMetric;
|
||||
|
||||
pub fn new_metric() -> MixMetric {
|
||||
MixMetric {
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::presence::CocoPresence;
|
||||
use crate::presence::CocoPresence;
|
||||
use reqwest::Response;
|
||||
|
||||
pub struct Request {
|
||||
@@ -73,13 +73,14 @@ mod metrics_get_request {
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::clients::directory::presence::CocoPresence;
|
||||
use crate::presence::CocoPresence;
|
||||
|
||||
pub fn new_presence() -> CocoPresence {
|
||||
CocoPresence {
|
||||
host: "foo.com".to_string(),
|
||||
pub_key: "abc".to_string(),
|
||||
last_seen: 666,
|
||||
version: "0.2.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::presence::MixNodePresence;
|
||||
use crate::presence::MixNodePresence;
|
||||
use reqwest::Response;
|
||||
|
||||
pub struct Request {
|
||||
@@ -73,7 +73,7 @@ mod metrics_get_request {
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::clients::directory::presence::MixNodePresence;
|
||||
use crate::presence::MixNodePresence;
|
||||
|
||||
pub fn new_presence() -> MixNodePresence {
|
||||
MixNodePresence {
|
||||
@@ -81,6 +81,7 @@ mod metrics_get_request {
|
||||
pub_key: "abc".to_string(),
|
||||
layer: 1,
|
||||
last_seen: 0,
|
||||
version: "0.1.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::presence::MixProviderPresence;
|
||||
use crate::presence::MixProviderPresence;
|
||||
use reqwest::Response;
|
||||
|
||||
pub struct Request {
|
||||
@@ -72,13 +72,16 @@ mod metrics_get_request {
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod fixtures {
|
||||
use crate::clients::directory::presence::MixProviderPresence;
|
||||
use crate::presence::MixProviderPresence;
|
||||
|
||||
pub fn new_presence() -> MixProviderPresence {
|
||||
MixProviderPresence {
|
||||
host: "foo.com".to_string(),
|
||||
client_listener: "foo.com".to_string(),
|
||||
mixnet_listener: "foo.com".to_string(),
|
||||
pub_key: "abc".to_string(),
|
||||
registered_clients: vec![],
|
||||
last_seen: 0,
|
||||
version: "0.1.0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-15
@@ -1,4 +1,4 @@
|
||||
use crate::clients::directory::presence::Topology;
|
||||
use crate::presence::Topology;
|
||||
|
||||
pub struct Request {
|
||||
base_url: String,
|
||||
@@ -75,25 +75,29 @@ mod topology_requests {
|
||||
"host": "3.8.244.109:4000",
|
||||
"pubKey": "AAAAAAAAAAEKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEFuhRpy4HfcuxwcRI9sIWMo_LVmbk19g1gfMRlBrmZqoEQL6rDApVLZ9eMp-5IQK8WLlZpWf4Zjy7kZolARAyp_rHUQkH4PrDjgoPrKbm6qK_iejYpL7qx28Q3VeInMpwMIMaSbbW9y36sEVtGc2I0Iu5vS0sp8ESiVlQ5NaBz72deZ8oKJJ4IEPPHP99-b0UQX80fVIrNM88mMzKy0bHri9NFlmIG-e0G1cqmw_ry3XWGQkcr1M5RuNa6oX50w5QawAEVxd5FP5bE8bS4x54Csof11sQWUTwMp6Q7_3H7ZCTSlKSqujlOhmfqSHfGPO2sDIYPHDhDzjakZpKAZWWhn_hiR6DfPpomQ01ZYUhVKKSMxz7_VPjsQplP0bZXA2gfnkADUN8UQ0N9g_usIw73r4aZsOviMsRM8oByvsjVfUWc4_HTLSdnQyImFkHz9CiCmrIYL2dYQRePRatWggvBAyeRzntxI4jDqLKiBdi54ZlAKgV6MCRaJ7Bu7BtmLXrtK4sawAED3QYxuvOSZrbZdUr4yG-U9yVvJ9Klkf-5Mo4EYp3qTL2KBB6_LrZepjAQqp486YkZ03mTIezcsZ48EboXVTWKBZ3QnTI5tX-j4gGxQb7klOJc97qJkDxsvpz4F0ChgCUIZhpIItWHia7_R3Gi-b5siLIdQdUho9isn3kiDGm6t0NED2Bgy3ZxxQwzqsBZm4kPr2_fPX4YyvIoP9895YcGjZyE5iiRC_TE41RJmB1GZYdxegTMq3lNDllKgiqaiPgawAEJASDkmZHTwlg9YOev5OWpQD-FnhPkqVNo_QcDyRu9eoGcWSGFp2sYqjG2SpmiXq0VNnAO7AcKxRzDFu7TjfhlU3Kt0uTKIcrWVU1zFNbJNMjYEq90pp50nowwx8INz20IXET2ZNX6kIXYFCsEvPLZFlG2OoL6xg3uQS1qMl3lIS_VxdO_JfVe0rT65WsJ_P4Nkc1jYiuNPHY6d_iFO0BVYqX0sOCX73GC_TT13BR0jnPwDAVw0rGtYHsXBb8TKOsawAEZIClauuT1V3qOZnb7uRZhFXO-PKTxgc1LCzJt2ChOrMZaBpjlkf3IPpJ2UF4JH4kGaDeBf2k_S-FLAs3drK21efbi5P6_a4QTxAiiRimXGoQIyvOg462s6kP_ZRFufo8YYQHS4olaOeqU4564dNskg_uBPsFMz_2GNOhmn_15cJqP1jfkyD49Z16GTS5YLHgVl9bJKqyvLuypsToLbt1BJzipEP0L2OohuRm-_MvqvwwWKyjNQsubgee1K728d9AawAEBkGggcNVCtXyhoSqi3_w0tVxtkAYeud8sBeAtZHGs06me_QL8co0MFLlO-zdkUb4ZBq08rFEbgLOma8_3whleM8NIPaHNISp1q3IsIhB5zdXcZoGsqLixODBFHtID3YEHAlr4f9T_yh11yJ95xGCl_6Y37hpwLQVGyrfSfccM24mVFqnV3TT5Wdq3ile-jesUx1Q2G1yK_xVqc6itmk-kDuBjyZgzYi1-jsIXAjnhM9G7t8J_Bv5yGGZhLK2dCzM=",
|
||||
"type": "validator",
|
||||
"lastSeen": 1575915097085539300
|
||||
"lastSeen": 1575915097085539300,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "3.9.129.61:4000",
|
||||
"pubKey": "AAAAAAAAAAMKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEEv6RMevAQmLGkeK0uJKnMPPAtm8GgXjWSQijYdnxlPh5SJSNeJUbPZKWFFWdk8yIFXKa8jnzETtdGFKgUUt5AVUDpTBmEdwaHCzlFhXrttshy0V5OhPUlV8cGABmxbagMYm0bFPg0r-snSkrB9YG6wqJYQVeIMOCGYCPbHmDA8R_0-h8VkRKWs1d9KvQOK4kShqgZtYN71KJW8uDE4q2jsGDVvxFt1AgmU9b93xsXF17KrpZy5WxlLZ73HtnTD_oawAED4vd_rK-Kx_n8x_OdDiiEOPUlYDlDCQUqenU9XHKH3B6ijfkJ368wd3LDDVStjDwNORrAyUSw_VlSNUpd1XLC8d17gTaIq5ZI2fWuwwZaoN1JCsYU8fQ6USgtIehQX7IPP8EkFuNmuCBCmpr4schtYniGe9J8Q4dsV-TYPr2uLJkdx1r7luzF--I22k7NfQQM14QDci_0kgrgmZ54CJGkjXyOhCppBXg3fqLC6aFvT3ZocfiiXBJt0huGgPMDtYsawAECLh8KUdNsDolERwJ8v04bS5jI_KKf7uUnCHWuCELwbJSUI3OK1ufS1qSpauvSzVQSbrhEzrEfwQn4VtxQxJlX4UdDU-R-hafiZvVC6DLLAbuORBAC3FScn9W58CnezH4DvCp_w7nftDfdxeuungbZT9XaxS3iNC6PnFsWF6WM3DxMwrzOrFe6wEEoTSPe1mcUDrtwM5UksIvJr6MBRAXrdl0IdBTQr7cLwKe_KYi4siwdjfJEJtOh7oxQBxBg2UkawAEJAPZK2Gg2MQwpxdDT24lNQHF7FVfkO_LuhJwn0RbwNDSVeA4P6-tWL5TkCpqr8xYHfwQ6Z3ILfpGCZr8PspwIoRzqZHQ16f8Pq9xnr0hLEI9BOQU0FS2EtuyPgju5iwsAJAfehUzu6kNLphuLGsXoIZdXDG5mbylwh9JzAVXTwgaR0hNqyXVJxgbt7jcYaSEBFcMGV-hjXyVVNzBleE-G9o_noI_KWU4Ce7K-qOMcewMKfy_VEw-gVaD6dHz6AMoawAEE9XuOLwRttvKybAssZ9gsK-_YRUwuFOeRDIr3NX___9bx6pCc18adCIlH_8EJWFwXZ05ZpNNE88mYx7ZQ3aqaArZJRoWeZeKhqH_s05V10xbzkYX71G5cqz--8vr9ZlQRb2BeETF_Tdq_PLk7qbT8WTGIoq7ZwyDRQTgzvkCgyzj_hBLh2o7sSVNgUo38SFUTMn7YtvVFYlSrTDE3WKE-T-nh5SWdDBxgDTc3Bw8JpzNH-WkoJ4Lim7sB4Op1gEUawAEW4-kenlffwsNr_3b3aV0YuusLpxB03sxPzQ5B0CWNiVtbja1Z4tWhKGUUrdq_eUgMV0y5Of-BqNi5FspAQnhJBFSSxtOzRGV1h3qyUTksfZyed9z8zPI-ZPP9XXm7hYgJgDz_kxte-NfS9UG9q5AZetHUN4kGxXutjjzfUQZ9yTvhBKgKgTI2Dp_R_jZrWQ8F1BoWzIJzjddT1K2MvCQEkARYw08isbOeFmCwgVUcjxYZO45WyOmLQA7QJRL9WvA=",
|
||||
"type": "validator",
|
||||
"lastSeen": 1575915097388409000
|
||||
"lastSeen": 1575915097388409000,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "3.9.222.1:4000",
|
||||
"pubKey": "AAAAAAAAAAQKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAECh9xcxpjOp1r7kiNIgrI9GgAlvXwgHkTchOxUiyOzTq6FDWdGN64KiC3NDeyGTg8FmzvGzS3jREeJqOdr4G9ZGtWkauAITgLFiH62t-YntRslhr8_1shxlmzKiNKJN_QFflEq79pZIlWtp3N8LIHMvXRtl-zt2DMze4s02XDmEkviyVE4CkQUDtCc-2MfPT4JcmEFqtFIxjrXn18SbYg3c6XUQHsGIkuDrKuCTRlpC8kvmM0uVoIeWdmwDlZk4jUawAEJhRwK5ozjqIWRP1bFzBPS9VhaJnfKU9PeFYtN5beiAHrYr2ylIB3yDfmAQUdKDowDUm5nfJATejEjEnrTGxh70QtfoNV391rSns3F71tBwY62KLaNr8qnVfeSFHV3FcQTMHHF_8mDb5_11Rj6aiMvW0y6eetHo7CDPMdEyDPmok_U2ZM5BzOUnwjT21HtnvcKxKKwHJ_QGfnAHPyDIhNOMgxJCrVazOidLCHeYGpyCLw1ipeTyKOQX0_ByB8dH6AawAEGV1GuF5SSlT67B1ityPJK2ZwXjeeKB4gGdCG3qRtWxLTZfGhVm7YAYm2f5tw_wrsJAZ9FubVhateGg0ZN67NxZtsvOOejXz6743f7ijnQopPgd_8pH-iVf6BEcSO8ZdcHxNRUTayzjVLs99bwMo2zaPevW4X4G_bN4mh---aPkdGYHwaiklzUhqJ-eqycrYAFyjyEXaPBXLQm1rpczqluNvnKbd8Q9LZWukgm7_uWv_HxufIvdWgoq8bAt78UU3oawAEP9VDehhqrQG5-WHMB66XVxo1TgMM8aVV0SwAq3lCRkpiFBz_9kw8T1F9Hx2AiNrEGT1QLbdMkpms1cG_5gBBahQofdt_NmUs1jfTFXY9iyMy1Q7A6ZYaLP8Z6q-orc1cKqySY-BJZQ_CpGFfXS0OVniFDQ6v78ytPK7K-yRgT1PxFgm3rZqrG0Tjbrpsg2PUL5S5fuXfMhUosP0uoLj0D1guWAR9Y7kfFBIXaTSFMoa8fghVBUTRNhK9f72a8SxQawAEOiv71taLjKqaaWQ_QjcDhWbvjG1EnsCyI0toNjGkcF19x4Vk-5NC96_4ioUGz404IC0XN03roRnibRT_78D9vZFVCWCqve9EjdF5TcApx03zIP4JT2g2q0MKIGgGrwt4Pz6LO6yOfMm7B8Yraps8IV-nP1w7K1m9XKP_FvH8egl5GHJe-_omlC2YyL_b28jMLENbxDFD-3KPjZFBhSLrRukX2PlayYTwEiTtokA2R9_11vQvJgP8KFEjGHg6zsAMawAEBn2H_hz2knb8ltnpEA5YSKVcV3nUtojkCNi_WUz7xUKd7efw1oI_lbnKrS7HkyC0JkQUZ1pCWUlSXNmgjMEhsn823a1LFzpV7rOv4vayYvvFX61hB9R78VjpyxJiYpDwRZLiUY3AK4WY8NqFDbjXR7rT4CkFHEf-VhSQQ8ZNvlpod1nmeVQVizHH9e7Tq7wsWz-LWEk3Hx6LmcrgDsL79LZYG9JXU5IdvG8RvLNx9cSwEI8yxcchpISAaot7UoYQ=",
|
||||
"type": "validator",
|
||||
"lastSeen": 1575915094734973000
|
||||
"lastSeen": 1575915094734973000,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "3.9.102.214:4000",
|
||||
"pubKey": "AAAAAAAAAAIKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEOVCUN3EwiVroS5-TOq2o7hYSxphK9X0G23N-IBZ0Tr1Rl8XEiJ-OEy0rqnAKwmhAZJWnx3u8oXqbZtOWIZmzQSpcoxhgwfhdmTZJCqT2RVzZyeFItX4sVeilEP3z2xdsJs8-a1kg6UZnx1s1BNLBo7eZrreZygWojPCIDBn03fSAflXoVc5PpY2CGy5MA_IgWgSYBHDdoZEtigp_amjqK7Us44Db20XpLxMXfbahiqa7WKNnMgi6Ca2H67VtaaD8awAEF3zbE1nZRAa7a8vbU25c80YBYJBaW8P6FwXQI-K0Xk5MakwYeMMnIrm6w6IS_0XAO5YlD453GLqnxY8H1BEnRpfOnT7PE4el9mJ8MuYQMo6R2up0lGCmYM0YA9FORjroM3ng69SEPfJPCReG7LfJkERl_m2U403ertDRBYrlqCDagDfyI500srBcMrjSvV3oNouyyx3yZUrjLQfbHhDteQFsYdmakJs8Y-Q9-5MXCcrz6Qa4xwv522Euv0CCxkHcawAEYjfsU_zDhUZA1ey1aquWXlFOnx-iEALqxW1slDYHwQ1M2SILc-v_E6i1doa5e_bAZHVezBHFAlaNAVedNyHFFJxYAqAK3hbzbvl2glw3Q6h_rTXElymloqtaqVFIJ-oUWWOHsZBmu8EDA-HzvGCiBa_GbRaVfh2lE4ObeMXoJrEm_5dbxxeEic2l3IYeIz40N9ooQQOkQcOZdY4AXWYCavIAwWEJBjLtptJgCLu9a_zM1S5GsiyJHpdDs46WbP0EawAEWZ-95Sf0YAHujxRNLdXgpqe0ZF8loVwzZfvyMvqaxF1Ug274BqHuY_c5NdPAzuqoTwjfEn8NKEoaNqlumM75FUYbaTd7mXvk4WVYWjVnkO40dfQjRB7DYhvj0LBlbndAJ4wJIA2ilPYgjZsXVbNNh3e2j3u9eABd0VaFMbSb8Sz5_31r8HzoWmPJs3HiyuyANGFUA6CvAnMN6K3b-D8BhFZU_nPUTgu80o8_n6LQt-XWbaC_mTHzsnOjzBiPJxlYawAEW3bmOEtStH2T8q7vMkhchImp2-hg9MFYGBmEe9sSByTn3NUf8eksqXOC1dUjHkXoZm298FgUYLkNdnlxWpf993j5mEDoFxjcTB7scBD7k6nu6Nrs_wK0-seS8gsHrx9UK7GwAsi10q82Cm4PFyAtrWjmy_d9WLHuZt6VIOKunTs8cf0FwNUiMcvZsruqIFJcP7iWxdiFdUkh65P_iCz1ZEjJcj2GEZoq4v3a3by1aizGPaaiKc1jd_T-XJg_YpncawAEWnstu5b9WiZv0x8xfsiMk6YRlU0Cnj5svxLLXz_8drvwAa--GBY5yH0ke2EM6udMEi2EPeFcGTe6Sjs0YEhSbY7Uad_8suD2J4tIWJSWBbiyvh7rSqzv57m7BlsVcHfQJn_wNH-UlC9xkx8vg-LwfN8_FlxvHNPTc7XZG3lKYbwpUWlZxAziOYT1VQ-2K2bQQBBMdix-ht_SjccL1Dc2dP5kDazQ8yZV_8xnyeheazEedWe63uutfkHlZRg9YwP8=",
|
||||
"type": "validator",
|
||||
"lastSeen": 1575915094967382800
|
||||
"lastSeen": 1575915094967382800,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
],
|
||||
"mixNodes": [
|
||||
@@ -101,42 +105,49 @@ mod topology_requests {
|
||||
"host": "35.176.155.107:1789",
|
||||
"pubKey": "zSob16499jT7C3S3ky4GihNOjlU6aLfSRkf1xAxOwV0=",
|
||||
"layer": 3,
|
||||
"lastSeen": 1575915096805374500
|
||||
"lastSeen": 1575915096805374500,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "18.130.86.190:1789",
|
||||
"pubKey": "vCdpFc0NvW0NSqsuTxtjFtiSY35aXesgT3JNA8sSIXk=",
|
||||
"layer": 1,
|
||||
"lastSeen": 1575915097370376000
|
||||
"lastSeen": 1575915097370376000,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "3.10.22.152:1789",
|
||||
"pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=",
|
||||
"layer": 1,
|
||||
"lastSeen": 1575915097639423500
|
||||
"lastSeen": 1575915097639423500,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "35.178.213.77:1789",
|
||||
"pubKey": "nkkrUjgL8UJk05QydvWvFSvtRB6nmeV8RMvH5540J3s=",
|
||||
"layer": 2,
|
||||
"lastSeen": 1575915097895166500
|
||||
"lastSeen": 1575915097895166500,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "52.56.99.196:1789",
|
||||
"pubKey": "whHuBuEc6zyOZOquKbuATaH4Crml61V_3Y-MztpWhF4=",
|
||||
"layer": 2,
|
||||
"lastSeen": 1575915096255174700
|
||||
"lastSeen": 1575915096255174700,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "3.9.12.238:1789",
|
||||
"pubKey": "vk5Sr-Xyi0cTbugACv8U42ZJ6hs6cGDox0rpmXY94Fc=",
|
||||
"layer": 3,
|
||||
"lastSeen": 1575915096497827600
|
||||
"lastSeen": 1575915096497827600,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
],
|
||||
"mixProviderNodes": [
|
||||
{
|
||||
"host": "3.8.176.11:1789",
|
||||
"clientListener": "3.8.176.11:8888",
|
||||
"mixnetListener": "3.8.176.11:9999",
|
||||
"pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=",
|
||||
"registeredClients": [
|
||||
{
|
||||
@@ -248,10 +259,12 @@ mod topology_requests {
|
||||
"pubKey": "COGdpfhmzNGR6YX820GqJIkjOihL8mr6-h-d3JlTDFA="
|
||||
}
|
||||
],
|
||||
"lastSeen": 1575915097358694100
|
||||
"lastSeen": 1575915097358694100,
|
||||
"version": "0.1.0"
|
||||
},
|
||||
{
|
||||
"host": "35.178.212.193:1789",
|
||||
"clientListener": "3.8.176.12:8888",
|
||||
"mixnetListener": "3.8.176.12:9999",
|
||||
"pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=",
|
||||
"registeredClients": [
|
||||
{
|
||||
@@ -297,7 +310,8 @@ mod topology_requests {
|
||||
"pubKey": "w1bfLpnd3rWu5JczB0nQfnE2S6nUCbx2AA7HDE48DQo="
|
||||
}
|
||||
],
|
||||
"lastSeen": 1575915097869025000
|
||||
"lastSeen": 1575915097869025000,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
]
|
||||
}"#.to_string()
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "mix-client"
|
||||
version = "0.1.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]
|
||||
log = "0.4.8"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
@@ -1,4 +1,4 @@
|
||||
use sphinx::route::NodeAddressBytes;
|
||||
use log::*;
|
||||
use sphinx::SphinxPacket;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::prelude::*;
|
||||
@@ -18,7 +18,7 @@ impl MixClient {
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bytes = packet.to_bytes();
|
||||
|
||||
println!("socket addr: {:?}", mix_addr);
|
||||
info!("socket addr: {:?}", mix_addr);
|
||||
|
||||
let mut stream = tokio::net::TcpStream::connect(mix_addr).await?;
|
||||
stream.write_all(&bytes[..]).await?;
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "provider-client"
|
||||
version = "0.1.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]
|
||||
futures = "0.3.1"
|
||||
log = "0.4.8"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
|
||||
## internal
|
||||
sfw-provider-requests = { path = "../../../sfw-provider/sfw-provider-requests" }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
@@ -1,4 +1,5 @@
|
||||
use futures::io::Error;
|
||||
use log::info;
|
||||
use sfw_provider_requests::requests::{ProviderRequest, PullRequest, RegisterRequest};
|
||||
use sfw_provider_requests::responses::{
|
||||
ProviderResponse, ProviderResponseError, PullResponse, RegisterResponse,
|
||||
@@ -51,13 +52,8 @@ impl ProviderClient {
|
||||
our_address: DestinationAddressBytes,
|
||||
auth_token: Option<AuthToken>,
|
||||
) -> Self {
|
||||
// DH temporary: the provider's client port is not in the topology, but we can't change that
|
||||
// right now without messing up the existing Go mixnet. So I'm going to hardcode this
|
||||
// for the moment until the Go mixnet goes away.
|
||||
let provider_socket = SocketAddr::new(provider_network_address.ip(), 9000);
|
||||
|
||||
ProviderClient {
|
||||
provider_network_address: provider_socket,
|
||||
provider_network_address,
|
||||
our_address,
|
||||
auth_token,
|
||||
}
|
||||
@@ -69,7 +65,7 @@ impl ProviderClient {
|
||||
|
||||
pub async fn send_request(&self, bytes: Vec<u8>) -> Result<Vec<u8>, ProviderClientError> {
|
||||
let mut socket = tokio::net::TcpStream::connect(self.provider_network_address).await?;
|
||||
println!("keep alive: {:?}", socket.keepalive());
|
||||
info!("keep alive: {:?}", socket.keepalive());
|
||||
socket.set_keepalive(Some(Duration::from_secs(2))).unwrap();
|
||||
socket.write_all(&bytes[..]).await?;
|
||||
if let Err(_e) = socket.shutdown(Shutdown::Write) {
|
||||
@@ -96,7 +92,7 @@ impl ProviderClient {
|
||||
let bytes = pull_request.to_bytes();
|
||||
|
||||
let response = self.send_request(bytes).await?;
|
||||
println!("Received the following response: {:?}", response);
|
||||
info!("Received the following response: {:?}", response);
|
||||
|
||||
let parsed_response = PullResponse::from_bytes(&response)?;
|
||||
Ok(parsed_response.messages)
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "validator-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>, David Hrycyszyn <dave@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "crypto"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.11.0"
|
||||
curve25519-dalek = "1.2.3"
|
||||
rand = "0.7.2"
|
||||
rand_os = "0.1"
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::PemStorable;
|
||||
|
||||
pub mod x25519;
|
||||
|
||||
pub trait MixnetEncryptionKeyPair<Priv, Pub>
|
||||
where
|
||||
Priv: MixnetEncryptionPrivateKey,
|
||||
Pub: MixnetEncryptionPublicKey,
|
||||
{
|
||||
fn new() -> Self;
|
||||
fn private_key(&self) -> &Priv;
|
||||
fn public_key(&self) -> &Pub;
|
||||
fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Self;
|
||||
|
||||
// TODO: encryption related methods
|
||||
}
|
||||
|
||||
pub trait MixnetEncryptionPublicKey:
|
||||
Sized + PemStorable + for<'a> From<&'a <Self as MixnetEncryptionPublicKey>::PrivateKeyMaterial>
|
||||
{
|
||||
// we need to couple public and private keys together
|
||||
type PrivateKeyMaterial: MixnetEncryptionPrivateKey<PublicKeyMaterial = Self>;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(b: &[u8]) -> Self;
|
||||
}
|
||||
|
||||
pub trait MixnetEncryptionPrivateKey: Sized + PemStorable {
|
||||
// we need to couple public and private keys together
|
||||
type PublicKeyMaterial: MixnetEncryptionPublicKey<PrivateKeyMaterial = Self>;
|
||||
|
||||
/// Returns the associated public key
|
||||
fn public_key(&self) -> Self::PublicKeyMaterial {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(b: &[u8]) -> Self;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
use crate::encryption::{
|
||||
MixnetEncryptionKeyPair, MixnetEncryptionPrivateKey, MixnetEncryptionPublicKey,
|
||||
};
|
||||
use crate::PemStorable;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
|
||||
// TODO: ensure this is a proper name for this considering we are not implementing entire DH here
|
||||
|
||||
const CURVE_GENERATOR: MontgomeryPoint = curve25519_dalek::constants::X25519_BASEPOINT;
|
||||
|
||||
pub struct KeyPair {
|
||||
pub(crate) private_key: PrivateKey,
|
||||
pub(crate) public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl MixnetEncryptionKeyPair<PrivateKey, PublicKey> for KeyPair {
|
||||
fn new() -> Self {
|
||||
let mut rng = rand_os::OsRng::new().unwrap();
|
||||
let private_key_value = Scalar::random(&mut rng);
|
||||
let public_key_value = CURVE_GENERATOR * private_key_value;
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(private_key_value),
|
||||
public_key: PublicKey(public_key_value),
|
||||
}
|
||||
}
|
||||
|
||||
fn private_key(&self) -> &PrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Self {
|
||||
KeyPair {
|
||||
private_key: PrivateKey::from_bytes(priv_bytes),
|
||||
public_key: PublicKey::from_bytes(pub_bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// COPY IS DERIVED ONLY TEMPORARILY UNTIL https://github.com/nymtech/nym/issues/47 is fixed
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct PrivateKey(pub Scalar);
|
||||
|
||||
impl MixnetEncryptionPrivateKey for PrivateKey {
|
||||
type PublicKeyMaterial = PublicKey;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.0.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn from_bytes(b: &[u8]) -> Self {
|
||||
let mut bytes = [0; 32];
|
||||
bytes.copy_from_slice(&b[..]);
|
||||
let key = Scalar::from_canonical_bytes(bytes).unwrap();
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorable for PrivateKey {
|
||||
fn pem_type(&self) -> String {
|
||||
String::from("X25519 PRIVATE KEY")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PublicKey(pub MontgomeryPoint);
|
||||
|
||||
impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
fn from(pk: &'a PrivateKey) -> Self {
|
||||
PublicKey(CURVE_GENERATOR * pk.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetEncryptionPublicKey for PublicKey {
|
||||
type PrivateKeyMaterial = PrivateKey;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.0.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn from_bytes(b: &[u8]) -> Self {
|
||||
let mut bytes = [0; 32];
|
||||
bytes.copy_from_slice(&b[..]);
|
||||
let key = MontgomeryPoint(bytes);
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorable for PublicKey {
|
||||
fn pem_type(&self) -> String {
|
||||
String::from("X25519 PUBLIC KEY")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use crate::encryption::{
|
||||
MixnetEncryptionKeyPair, MixnetEncryptionPrivateKey, MixnetEncryptionPublicKey,
|
||||
};
|
||||
use crate::{encryption, PemStorable};
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
|
||||
pub trait MixnetIdentityKeyPair<Priv, Pub>
|
||||
where
|
||||
Priv: MixnetIdentityPrivateKey,
|
||||
Pub: MixnetIdentityPublicKey,
|
||||
{
|
||||
fn new() -> Self;
|
||||
fn private_key(&self) -> &Priv;
|
||||
fn public_key(&self) -> &Pub;
|
||||
fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Self;
|
||||
|
||||
// TODO: signing related methods
|
||||
}
|
||||
|
||||
pub trait MixnetIdentityPublicKey:
|
||||
Sized + PemStorable + for<'a> From<&'a <Self as MixnetIdentityPublicKey>::PrivateKeyMaterial>
|
||||
{
|
||||
// we need to couple public and private keys together
|
||||
type PrivateKeyMaterial: MixnetIdentityPrivateKey<PublicKeyMaterial = Self>;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(b: &[u8]) -> Self;
|
||||
}
|
||||
|
||||
pub trait MixnetIdentityPrivateKey: Sized + PemStorable {
|
||||
// we need to couple public and private keys together
|
||||
type PublicKeyMaterial: MixnetIdentityPublicKey<PrivateKeyMaterial = Self>;
|
||||
|
||||
/// Returns the associated public key
|
||||
fn public_key(&self) -> Self::PublicKeyMaterial {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn from_bytes(b: &[u8]) -> Self;
|
||||
}
|
||||
|
||||
// same for validator
|
||||
|
||||
// for time being define a dummy identity using x25519 encryption keys (as we've done so far)
|
||||
// and replace it with proper keys, like ed25519 later on
|
||||
|
||||
pub struct DummyMixIdentityKeyPair {
|
||||
pub private_key: DummyMixIdentityPrivateKey,
|
||||
pub public_key: DummyMixIdentityPublicKey,
|
||||
}
|
||||
|
||||
impl MixnetIdentityKeyPair<DummyMixIdentityPrivateKey, DummyMixIdentityPublicKey>
|
||||
for DummyMixIdentityKeyPair
|
||||
{
|
||||
fn new() -> Self {
|
||||
let keypair = encryption::x25519::KeyPair::new();
|
||||
DummyMixIdentityKeyPair {
|
||||
private_key: DummyMixIdentityPrivateKey(keypair.private_key),
|
||||
public_key: DummyMixIdentityPublicKey(keypair.public_key),
|
||||
}
|
||||
}
|
||||
|
||||
fn private_key(&self) -> &DummyMixIdentityPrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &DummyMixIdentityPublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Self {
|
||||
DummyMixIdentityKeyPair {
|
||||
private_key: DummyMixIdentityPrivateKey::from_bytes(priv_bytes),
|
||||
public_key: DummyMixIdentityPublicKey::from_bytes(pub_bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct DummyMixIdentityPublicKey(encryption::x25519::PublicKey);
|
||||
|
||||
impl MixnetIdentityPublicKey for DummyMixIdentityPublicKey {
|
||||
type PrivateKeyMaterial = DummyMixIdentityPrivateKey;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(b: &[u8]) -> Self {
|
||||
Self(encryption::x25519::PublicKey::from_bytes(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorable for DummyMixIdentityPublicKey {
|
||||
fn pem_type(&self) -> String {
|
||||
format!("DUMMY KEY BASED ON {}", self.0.pem_type())
|
||||
}
|
||||
}
|
||||
|
||||
impl DummyMixIdentityPublicKey {
|
||||
pub fn to_b64_string(&self) -> String {
|
||||
base64::encode_config(&self.to_bytes(), base64::URL_SAFE)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn from_b64_string(val: String) -> Self {
|
||||
Self::from_bytes(&base64::decode_config(&val, base64::URL_SAFE).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// COPY IS DERIVED ONLY TEMPORARILY UNTIL https://github.com/nymtech/nym/issues/47 is fixed
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct DummyMixIdentityPrivateKey(pub encryption::x25519::PrivateKey);
|
||||
|
||||
impl<'a> From<&'a DummyMixIdentityPrivateKey> for DummyMixIdentityPublicKey {
|
||||
fn from(pk: &'a DummyMixIdentityPrivateKey) -> Self {
|
||||
let private_ref = &pk.0;
|
||||
let public: encryption::x25519::PublicKey = private_ref.into();
|
||||
DummyMixIdentityPublicKey(public)
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnetIdentityPrivateKey for DummyMixIdentityPrivateKey {
|
||||
type PublicKeyMaterial = DummyMixIdentityPublicKey;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(b: &[u8]) -> Self {
|
||||
Self(encryption::x25519::PrivateKey::from_bytes(b))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this will be implemented differently by using the proper trait
|
||||
impl DummyMixIdentityPrivateKey {
|
||||
pub fn as_scalar(self) -> Scalar {
|
||||
let encryption_key = self.0;
|
||||
encryption_key.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorable for DummyMixIdentityPrivateKey {
|
||||
fn pem_type(&self) -> String {
|
||||
format!("DUMMY KEY BASED ON {}", self.0.pem_type())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod encryption;
|
||||
pub mod identity;
|
||||
|
||||
// TODO: this trait will need to be moved elsewhere, probably to some 'persistence' crate
|
||||
// but since it will need to be used by all identities, it's not really appropriate if it lived in nym-client
|
||||
|
||||
pub trait PemStorable {
|
||||
fn pem_type(&self) -> String;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "healthcheck"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.104"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
|
||||
## internal
|
||||
addressing = {path = "../addressing" }
|
||||
crypto = { path = "../crypto" }
|
||||
directory-client = { path = "../clients/directory-client" }
|
||||
mix-client = { path = "../clients/mix-client" }
|
||||
provider-client = { path = "../clients/provider-client" }
|
||||
sfw-provider-requests = { path = "../../sfw-provider/sfw-provider-requests" }
|
||||
topology = {path = "../topology" }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -0,0 +1,15 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct HealthCheck {
|
||||
#[serde(rename(deserialize = "directory-server"))]
|
||||
pub directory_server: String,
|
||||
|
||||
pub interval: f64, // in seconds
|
||||
|
||||
#[serde(rename(deserialize = "resolution-timeout"))]
|
||||
pub resolution_timeout: f64, // in seconds
|
||||
|
||||
#[serde(rename(deserialize = "test-packets-per-node"))]
|
||||
pub num_test_packets: usize,
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use crate::result::HealthCheckResult;
|
||||
use directory_client::requests::presence_topology_get::PresenceTopologyGetRequester;
|
||||
use directory_client::DirectoryClient;
|
||||
use log::{debug, error, info, trace};
|
||||
use std::fmt::{Error, Formatter};
|
||||
use std::time::Duration;
|
||||
use topology::NymTopologyError;
|
||||
|
||||
pub mod config;
|
||||
mod path_check;
|
||||
mod result;
|
||||
mod score;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HealthCheckerError {
|
||||
FailedToObtainTopologyError,
|
||||
InvalidTopologyError,
|
||||
}
|
||||
|
||||
// required by std::error::Error
|
||||
impl std::fmt::Display for HealthCheckerError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
// just have implementation equivalent to derived debug
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HealthCheckerError {}
|
||||
|
||||
impl From<topology::NymTopologyError> for HealthCheckerError {
|
||||
fn from(_: NymTopologyError) -> Self {
|
||||
use HealthCheckerError::*;
|
||||
InvalidTopologyError
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HealthChecker {
|
||||
directory_client: directory_client::Client,
|
||||
interval: Duration,
|
||||
num_test_packets: usize,
|
||||
resolution_timeout: Duration,
|
||||
}
|
||||
|
||||
impl HealthChecker {
|
||||
pub fn new(config: config::HealthCheck) -> Self {
|
||||
debug!(
|
||||
"healthcheck will be using the following directory server: {:?}",
|
||||
config.directory_server
|
||||
);
|
||||
let directory_client_config = directory_client::Config::new(config.directory_server);
|
||||
HealthChecker {
|
||||
directory_client: directory_client::Client::new(directory_client_config),
|
||||
interval: Duration::from_secs_f64(config.interval),
|
||||
resolution_timeout: Duration::from_secs_f64(config.resolution_timeout),
|
||||
num_test_packets: config.num_test_packets,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn do_check(&self) -> Result<HealthCheckResult, HealthCheckerError> {
|
||||
trace!("going to perform a healthcheck!");
|
||||
let current_topology = match self.directory_client.presence_topology.get() {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
error!("failed to obtain topology - {:?}", err);
|
||||
return Err(HealthCheckerError::FailedToObtainTopologyError);
|
||||
}
|
||||
};
|
||||
trace!("current topology: {:?}", current_topology);
|
||||
|
||||
let mut healthcheck_result = HealthCheckResult::calculate(
|
||||
current_topology,
|
||||
self.num_test_packets,
|
||||
self.resolution_timeout,
|
||||
)
|
||||
.await;
|
||||
healthcheck_result.sort_scores();
|
||||
Ok(healthcheck_result)
|
||||
}
|
||||
|
||||
pub async fn run(self) -> Result<(), HealthCheckerError> {
|
||||
debug!(
|
||||
"healthcheck will run every {:?} and will send {} packets to each node",
|
||||
self.interval, self.num_test_packets
|
||||
);
|
||||
|
||||
loop {
|
||||
match self.do_check().await {
|
||||
Ok(health) => info!("current network health: \n{}", health),
|
||||
Err(err) => error!("failed to perform healthcheck - {:?}", err),
|
||||
};
|
||||
|
||||
tokio::time::delay_for(self.interval).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
use crypto::identity::{DummyMixIdentityKeyPair, MixnetIdentityKeyPair, MixnetIdentityPublicKey};
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, trace, warn};
|
||||
use mix_client::MixClient;
|
||||
use provider_client::ProviderClient;
|
||||
use sphinx::header::delays::Delay;
|
||||
use sphinx::route::{Destination, Node as SphinxNode};
|
||||
use std::collections::HashMap;
|
||||
use topology::MixProviderNode;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PathStatus {
|
||||
Healthy,
|
||||
Unhealthy,
|
||||
Pending,
|
||||
}
|
||||
|
||||
pub(crate) struct PathChecker {
|
||||
provider_clients: HashMap<[u8; 32], Option<ProviderClient>>,
|
||||
// currently this is an overkill as MixClient is extremely cheap to create,
|
||||
// however, once we introduce persistent connection between client and layer one mixes,
|
||||
// this will be extremely helpful to have
|
||||
layer_one_clients: HashMap<[u8; 32], Option<MixClient>>,
|
||||
paths_status: HashMap<Vec<u8>, PathStatus>,
|
||||
our_destination: Destination,
|
||||
}
|
||||
|
||||
impl PathChecker {
|
||||
pub(crate) async fn new(
|
||||
providers: Vec<MixProviderNode>,
|
||||
ephemeral_keys: DummyMixIdentityKeyPair,
|
||||
) -> Self {
|
||||
let mut provider_clients = HashMap::new();
|
||||
|
||||
let mut temporary_address = [0u8; 32];
|
||||
let public_key_bytes = ephemeral_keys.public_key().to_bytes();
|
||||
temporary_address.copy_from_slice(&public_key_bytes[..]);
|
||||
|
||||
for provider in providers {
|
||||
let mut provider_client =
|
||||
ProviderClient::new(provider.client_listener, temporary_address, None);
|
||||
let insertion_result = match provider_client.register().await {
|
||||
Ok(token) => {
|
||||
debug!("registered at provider {}", provider.pub_key);
|
||||
provider_client.update_token(token);
|
||||
provider_clients.insert(provider.get_pub_key_bytes(), Some(provider_client))
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to register at provider {} - {:?}",
|
||||
provider.pub_key, err
|
||||
);
|
||||
provider_clients.insert(provider.get_pub_key_bytes(), None)
|
||||
}
|
||||
};
|
||||
|
||||
if insertion_result.is_some() {
|
||||
error!("provider {} already existed!", provider.pub_key);
|
||||
}
|
||||
}
|
||||
|
||||
PathChecker {
|
||||
provider_clients,
|
||||
layer_one_clients: HashMap::new(),
|
||||
our_destination: Destination::new(temporary_address, Default::default()),
|
||||
paths_status: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// iteration is used to distinguish packets sent through the same path (as the healthcheck
|
||||
// may try to send say 10 packets through given path)
|
||||
fn unique_path_key(path: &Vec<SphinxNode>, iteration: u8) -> Vec<u8> {
|
||||
std::iter::once(iteration)
|
||||
.chain(
|
||||
path.iter()
|
||||
.map(|node| node.pub_key.to_bytes().to_vec())
|
||||
.flatten(),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn path_key_to_node_keys(path_key: Vec<u8>) -> Vec<[u8; 32]> {
|
||||
assert_eq!(path_key.len() % 32, 1);
|
||||
path_key
|
||||
.into_iter()
|
||||
.skip(1) // remove first byte as it represents the iteration number which we do not care about now
|
||||
.chunks(32)
|
||||
.into_iter()
|
||||
.map(|key_chunk| {
|
||||
let key_chunk_vec: Vec<_> = key_chunk.collect();
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(&key_chunk_vec);
|
||||
key
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn update_path_statuses(&mut self, messages: Vec<Vec<u8>>) {
|
||||
for msg in messages.into_iter() {
|
||||
// mark path as healthy
|
||||
let previous_status = self.paths_status.insert(msg, PathStatus::Healthy);
|
||||
match previous_status {
|
||||
None => warn!("we received information about unknown path! - perhaps somebody is messing with healthchecker?"),
|
||||
Some(status) => {
|
||||
if status != PathStatus::Pending {
|
||||
warn!("we received information about path that WASN'T in PENDING state! (it was in {:?}", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// consume path_checker and return all path statuses
|
||||
pub(crate) fn get_all_statuses(self) -> HashMap<Vec<u8>, PathStatus> {
|
||||
self.paths_status
|
||||
}
|
||||
|
||||
// pull messages from given provider until there are no more 'real' messages
|
||||
async fn resolve_pending_provider_checks(
|
||||
&self,
|
||||
provider_client: &ProviderClient,
|
||||
) -> Vec<Vec<u8>> {
|
||||
// keep getting messages until we encounter the dummy message
|
||||
let mut provider_messages = Vec::new();
|
||||
loop {
|
||||
match provider_client.retrieve_messages().await {
|
||||
Err(err) => {
|
||||
error!("failed to fetch provider messages! - {:?}", err);
|
||||
break;
|
||||
}
|
||||
Ok(messages) => {
|
||||
let mut should_stop = false;
|
||||
for msg in messages.into_iter() {
|
||||
trace!("received provider response: {:?}", msg);
|
||||
if msg == sfw_provider_requests::DUMMY_MESSAGE_CONTENT {
|
||||
// finish iterating the loop as the messages might not be ordered
|
||||
should_stop = true;
|
||||
} else {
|
||||
provider_messages.push(msg);
|
||||
}
|
||||
}
|
||||
if should_stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
provider_messages
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_pending_checks(&mut self) {
|
||||
// not sure how to nicely put it into an iterator due to it being async calls
|
||||
let mut provider_messages = Vec::new();
|
||||
for provider_client in self.provider_clients.values() {
|
||||
// if it was none all associated paths were already marked as unhealthy
|
||||
if provider_client.is_some() {
|
||||
let pc = provider_client.as_ref().unwrap();
|
||||
provider_messages.extend(self.resolve_pending_provider_checks(pc).await);
|
||||
}
|
||||
}
|
||||
|
||||
self.update_path_statuses(provider_messages);
|
||||
}
|
||||
|
||||
pub(crate) async fn send_test_packet(&mut self, path: &Vec<SphinxNode>, iteration: u8) {
|
||||
debug!("Checking path: {:?} ({})", path, iteration);
|
||||
let path_identifier = PathChecker::unique_path_key(path, iteration);
|
||||
|
||||
// check if there is even any point in sending the packet
|
||||
|
||||
// does provider exist?
|
||||
let provider_client = self
|
||||
.provider_clients
|
||||
.get(&path.last().unwrap().pub_key.to_bytes())
|
||||
.unwrap();
|
||||
|
||||
if provider_client.is_none() {
|
||||
debug!("we can ignore this path as provider itself is inaccessible");
|
||||
if self
|
||||
.paths_status
|
||||
.insert(path_identifier, PathStatus::Unhealthy)
|
||||
.is_some()
|
||||
{
|
||||
panic!("Overwriting path checks!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let layer_one_mix = path.first().unwrap();
|
||||
let first_node_key = layer_one_mix.pub_key.to_bytes();
|
||||
let first_node_address =
|
||||
addressing::socket_address_from_encoded_bytes(layer_one_mix.address.to_bytes());
|
||||
|
||||
let first_node_client = self
|
||||
.layer_one_clients
|
||||
.entry(first_node_key)
|
||||
.or_insert(Some(mix_client::MixClient::new()));
|
||||
|
||||
if first_node_client.is_none() {
|
||||
debug!("we can ignore this path as layer one mix is inaccessible");
|
||||
if self
|
||||
.paths_status
|
||||
.insert(path_identifier, PathStatus::Unhealthy)
|
||||
.is_some()
|
||||
{
|
||||
panic!("Overwriting path checks!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let first_node_client = first_node_client.as_ref().unwrap();
|
||||
|
||||
let delays: Vec<_> = path.iter().map(|_| Delay::new(0)).collect();
|
||||
|
||||
let packet = sphinx::SphinxPacket::new(
|
||||
path_identifier.clone(),
|
||||
&path[..],
|
||||
&self.our_destination,
|
||||
&delays,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
debug!("sending test packet to {}", first_node_address);
|
||||
match first_node_client.send(packet, first_node_address).await {
|
||||
Err(err) => {
|
||||
warn!("failed to send packet to {} - {}", first_node_address, err);
|
||||
if self
|
||||
.paths_status
|
||||
.insert(path_identifier, PathStatus::Unhealthy)
|
||||
.is_some()
|
||||
{
|
||||
panic!("Overwriting path checks!")
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
if self
|
||||
.paths_status
|
||||
.insert(path_identifier, PathStatus::Pending)
|
||||
.is_some()
|
||||
{
|
||||
panic!("Overwriting path checks!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
use crate::path_check::{PathChecker, PathStatus};
|
||||
use crate::score::NodeScore;
|
||||
use crypto::identity::{DummyMixIdentityKeyPair, MixnetIdentityKeyPair};
|
||||
use log::{debug, error, info, warn};
|
||||
use sphinx::route::NodeAddressBytes;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Error, Formatter};
|
||||
use std::time::Duration;
|
||||
use topology::NymTopology;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HealthCheckResult(Vec<NodeScore>);
|
||||
|
||||
impl std::fmt::Display for HealthCheckResult {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "NETWORK HEALTH\n==============\n")?;
|
||||
self.0
|
||||
.iter()
|
||||
.for_each(|score| write!(f, "{}\n", score).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HealthCheckResult {
|
||||
pub fn sort_scores(&mut self) {
|
||||
self.0.sort();
|
||||
}
|
||||
|
||||
fn zero_score<T: NymTopology>(topology: T) -> Self {
|
||||
warn!("The network is unhealthy, could not send any packets - returning zero score!");
|
||||
let mixes = topology.get_mix_nodes();
|
||||
let providers = topology.get_mix_provider_nodes();
|
||||
|
||||
let health = mixes
|
||||
.into_iter()
|
||||
.map(|node| NodeScore::from_mixnode(node))
|
||||
.chain(
|
||||
providers
|
||||
.into_iter()
|
||||
.map(|node| NodeScore::from_provider(node)),
|
||||
)
|
||||
.collect();
|
||||
|
||||
HealthCheckResult(health)
|
||||
}
|
||||
|
||||
// TODO: that is O(n) so maybe not the most efficient considering it will be called n times...
|
||||
fn node_score(&self, node_key: NodeAddressBytes) -> Option<f64> {
|
||||
self.0
|
||||
.iter()
|
||||
.find(|&node_score| node_score.pub_key() == node_key)
|
||||
.map(|node| node.score())
|
||||
}
|
||||
|
||||
pub fn filter_topology_by_score<T: NymTopology>(
|
||||
&self,
|
||||
topology: &T,
|
||||
score_threshold: f64,
|
||||
) -> T {
|
||||
let filtered_mix_nodes = topology
|
||||
.get_mix_nodes()
|
||||
.into_iter()
|
||||
.filter(|node| {
|
||||
match self.node_score(NodeAddressBytes::from_b64_string(node.pub_key.clone())) {
|
||||
None => {
|
||||
error!("Unknown node in topology - {:?}", node);
|
||||
false
|
||||
}
|
||||
Some(score) => score > score_threshold,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let filtered_provider_nodes = topology
|
||||
.get_mix_provider_nodes()
|
||||
.into_iter()
|
||||
.filter(|node| {
|
||||
match self.node_score(NodeAddressBytes::from_b64_string(node.pub_key.clone())) {
|
||||
None => {
|
||||
error!("Unknown node in topology - {:?}", node);
|
||||
false
|
||||
}
|
||||
Some(score) => score > score_threshold,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// coco nodes remain unchanged as no healthcheck is being run on them or time being
|
||||
let filtered_coco_nodes = topology.get_coco_nodes();
|
||||
|
||||
T::new_from_nodes(
|
||||
filtered_mix_nodes,
|
||||
filtered_provider_nodes,
|
||||
filtered_coco_nodes,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn calculate<T: NymTopology>(
|
||||
topology: T,
|
||||
iterations: usize,
|
||||
resolution_timeout: Duration,
|
||||
) -> Self {
|
||||
// currently healthchecker supports only up to 255 iterations - if we somehow
|
||||
// find we need more, it's relatively easy change
|
||||
assert!(iterations <= 255);
|
||||
|
||||
let all_paths = match topology.all_paths() {
|
||||
Ok(paths) => paths,
|
||||
Err(_) => return Self::zero_score(topology),
|
||||
};
|
||||
|
||||
// create entries for all nodes
|
||||
let mut score_map = HashMap::new();
|
||||
topology.get_mix_nodes().into_iter().for_each(|node| {
|
||||
score_map.insert(node.get_pub_key_bytes(), NodeScore::from_mixnode(node));
|
||||
});
|
||||
|
||||
topology
|
||||
.get_mix_provider_nodes()
|
||||
.into_iter()
|
||||
.for_each(|node| {
|
||||
score_map.insert(node.get_pub_key_bytes(), NodeScore::from_provider(node));
|
||||
});
|
||||
|
||||
let ephemeral_keys = DummyMixIdentityKeyPair::new();
|
||||
let providers = topology.get_mix_provider_nodes();
|
||||
|
||||
let mut path_checker = PathChecker::new(providers, ephemeral_keys).await;
|
||||
for i in 0..iterations {
|
||||
debug!("running healthcheck iteration {} / {}", i + 1, iterations);
|
||||
for path in &all_paths {
|
||||
path_checker.send_test_packet(&path, i as u8).await;
|
||||
// increase sent count for each node
|
||||
for node in path {
|
||||
let current_node_score = score_map.get_mut(&node.pub_key.0).unwrap();
|
||||
current_node_score.increase_sent_packet_count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"waiting {:?} for pending requests to resolve",
|
||||
resolution_timeout
|
||||
);
|
||||
tokio::time::delay_for(resolution_timeout).await;
|
||||
path_checker.resolve_pending_checks().await;
|
||||
|
||||
let all_statuses = path_checker.get_all_statuses();
|
||||
for (path_key, status) in all_statuses.into_iter() {
|
||||
let node_keys = PathChecker::path_key_to_node_keys(path_key);
|
||||
for node in node_keys {
|
||||
if status == PathStatus::Healthy {
|
||||
let current_node_score = score_map.get_mut(&node).unwrap();
|
||||
current_node_score.increase_received_packet_count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HealthCheckResult(score_map.into_iter().map(|(_, v)| v).collect())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use log::error;
|
||||
use sphinx::route::NodeAddressBytes;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Error;
|
||||
use std::fmt::Formatter;
|
||||
use std::net::SocketAddr;
|
||||
use topology::{MixNode, MixProviderNode};
|
||||
|
||||
// TODO: should 'nodetype' really be part of healthcheck::score
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
|
||||
pub(crate) enum NodeType {
|
||||
Mix,
|
||||
MixProvider,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NodeType {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
match self {
|
||||
NodeType::Mix => write!(f, "Mix"),
|
||||
NodeType::MixProvider => write!(f, "MixProvider"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq)]
|
||||
pub(crate) struct NodeScore {
|
||||
typ: NodeType,
|
||||
pub_key: NodeAddressBytes,
|
||||
addresses: Vec<SocketAddr>,
|
||||
version: String,
|
||||
layer: String,
|
||||
packets_sent: u64,
|
||||
packets_received: u64,
|
||||
}
|
||||
|
||||
impl Ord for NodeScore {
|
||||
// order by: version, layer, sent, received, pubkey; ignore addresses
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.typ > other.typ {
|
||||
return Ordering::Greater;
|
||||
} else if self.typ < other.typ {
|
||||
return Ordering::Less;
|
||||
}
|
||||
if self.version > other.version {
|
||||
return Ordering::Greater;
|
||||
} else if self.version < other.version {
|
||||
return Ordering::Less;
|
||||
}
|
||||
if self.layer > other.layer {
|
||||
return Ordering::Greater;
|
||||
} else if self.layer < other.layer {
|
||||
return Ordering::Less;
|
||||
}
|
||||
if self.packets_sent > other.packets_sent {
|
||||
return Ordering::Greater;
|
||||
} else if self.packets_sent < other.packets_sent {
|
||||
return Ordering::Less;
|
||||
}
|
||||
if self.packets_received > other.packets_received {
|
||||
return Ordering::Greater;
|
||||
} else if self.packets_received < other.packets_received {
|
||||
return Ordering::Less;
|
||||
}
|
||||
if self.pub_key > other.pub_key {
|
||||
return Ordering::Greater;
|
||||
} else if self.pub_key < other.pub_key {
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for NodeScore {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NodeScore {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.typ == other.typ
|
||||
&& self.pub_key == other.pub_key
|
||||
&& self.addresses == other.addresses
|
||||
&& self.version == other.version
|
||||
&& self.layer == other.layer
|
||||
&& self.packets_sent == other.packets_sent
|
||||
&& self.packets_received == other.packets_received
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeScore {
|
||||
pub(crate) fn from_mixnode(node: MixNode) -> Self {
|
||||
NodeScore {
|
||||
typ: NodeType::Mix,
|
||||
pub_key: NodeAddressBytes::from_b64_string(node.pub_key),
|
||||
addresses: vec![node.host],
|
||||
version: node.version,
|
||||
layer: format!("layer {}", node.layer),
|
||||
packets_sent: 0,
|
||||
packets_received: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_provider(node: MixProviderNode) -> Self {
|
||||
NodeScore {
|
||||
typ: NodeType::MixProvider,
|
||||
pub_key: NodeAddressBytes::from_b64_string(node.pub_key),
|
||||
addresses: vec![node.mixnet_listener, node.client_listener],
|
||||
version: node.version,
|
||||
layer: format!("provider"),
|
||||
packets_sent: 0,
|
||||
packets_received: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn increase_sent_packet_count(&mut self) {
|
||||
self.packets_sent += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn increase_received_packet_count(&mut self) {
|
||||
self.packets_received += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn score(&self) -> f64 {
|
||||
match self.packets_sent {
|
||||
0 => 0.0,
|
||||
_ => (self.packets_received as f64 / self.packets_sent as f64) * 100.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pub_key(&self) -> NodeAddressBytes {
|
||||
self.pub_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NodeScore {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
|
||||
let fmtd_addresses = match self.addresses.len() {
|
||||
1 => format!("{}", self.addresses[0]),
|
||||
2 => format!("{}, {}", self.addresses[0], self.addresses[1]),
|
||||
n => {
|
||||
error!(
|
||||
"could not format score - node has {} addresses while only 1 or 2 are allowed!",
|
||||
n
|
||||
);
|
||||
return Err(std::fmt::Error);
|
||||
}
|
||||
};
|
||||
let stringified_key = self.pub_key.to_b64_string();
|
||||
write!(
|
||||
f,
|
||||
"({})\t{}/{}\t({}%)\t|| {}\tv{} <{}> - {}",
|
||||
self.typ,
|
||||
self.packets_received,
|
||||
self.packets_sent,
|
||||
self.score(),
|
||||
self.layer,
|
||||
self.version,
|
||||
fmtd_addresses,
|
||||
stringified_key,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "topology"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.11.0"
|
||||
curve25519-dalek = "1.2.3"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.2"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
|
||||
## internal
|
||||
addressing = {path = "../addressing"}
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
@@ -0,0 +1,204 @@
|
||||
use addressing;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use itertools::Itertools;
|
||||
use rand::seq::IteratorRandom;
|
||||
use sphinx::route::{Node as SphinxNode, NodeAddressBytes};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MixNode {
|
||||
pub host: SocketAddr,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<SphinxNode> for MixNode {
|
||||
fn into(self) -> SphinxNode {
|
||||
let address_bytes = addressing::encoded_bytes_from_socket_address(self.host);
|
||||
let key_bytes = self.get_pub_key_bytes();
|
||||
let key = MontgomeryPoint(key_bytes);
|
||||
|
||||
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
|
||||
}
|
||||
}
|
||||
|
||||
impl MixNode {
|
||||
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
|
||||
let decoded_key_bytes = base64::decode_config(&self.pub_key, base64::URL_SAFE).unwrap();
|
||||
let mut key_bytes = [0; 32];
|
||||
key_bytes.copy_from_slice(&decoded_key_bytes[..]);
|
||||
key_bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MixProviderClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MixProviderNode {
|
||||
pub client_listener: SocketAddr,
|
||||
pub mixnet_listener: SocketAddr,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<MixProviderClient>,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<SphinxNode> for MixProviderNode {
|
||||
fn into(self) -> SphinxNode {
|
||||
let address_bytes = addressing::encoded_bytes_from_socket_address(self.mixnet_listener);
|
||||
let key_bytes = self.get_pub_key_bytes();
|
||||
let key = MontgomeryPoint(key_bytes);
|
||||
|
||||
SphinxNode::new(NodeAddressBytes::from_bytes(address_bytes), key)
|
||||
}
|
||||
}
|
||||
|
||||
impl MixProviderNode {
|
||||
pub fn get_pub_key_bytes(&self) -> [u8; 32] {
|
||||
let decoded_key_bytes = base64::decode_config(&self.pub_key, base64::URL_SAFE).unwrap();
|
||||
let mut key_bytes = [0; 32];
|
||||
key_bytes.copy_from_slice(&decoded_key_bytes[..]);
|
||||
key_bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CocoNode {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NymTopologyError {
|
||||
InvalidMixLayerError,
|
||||
MissingLayerError(Vec<u64>),
|
||||
}
|
||||
|
||||
pub trait NymTopology: Sized {
|
||||
fn new(directory_server: String) -> Self;
|
||||
fn new_from_nodes(
|
||||
mix_nodes: Vec<MixNode>,
|
||||
mix_provider_nodes: Vec<MixProviderNode>,
|
||||
coco_nodes: Vec<CocoNode>,
|
||||
) -> Self;
|
||||
fn get_mix_nodes(&self) -> Vec<MixNode>;
|
||||
fn get_mix_provider_nodes(&self) -> Vec<MixProviderNode>;
|
||||
fn get_coco_nodes(&self) -> Vec<CocoNode>;
|
||||
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<MixNode>>, NymTopologyError> {
|
||||
let mut layered_topology: HashMap<u64, Vec<MixNode>> = HashMap::new();
|
||||
let mut highest_layer = 0;
|
||||
for mix in self.get_mix_nodes() {
|
||||
// we need to have extra space for provider
|
||||
if mix.layer > sphinx::constants::MAX_PATH_LENGTH as u64 {
|
||||
return Err(NymTopologyError::InvalidMixLayerError);
|
||||
}
|
||||
highest_layer = max(highest_layer, mix.layer);
|
||||
|
||||
let layer_nodes = layered_topology.entry(mix.layer).or_insert(Vec::new());
|
||||
layer_nodes.push(mix);
|
||||
}
|
||||
|
||||
// verify the topology - make sure there are no gaps and there is at least one node per layer
|
||||
let mut missing_layers = Vec::new();
|
||||
for layer in 1..=highest_layer {
|
||||
if !layered_topology.contains_key(&layer) {
|
||||
missing_layers.push(layer);
|
||||
}
|
||||
if layered_topology[&layer].len() == 0 {
|
||||
missing_layers.push(layer);
|
||||
}
|
||||
}
|
||||
|
||||
if missing_layers.len() > 0 {
|
||||
return Err(NymTopologyError::MissingLayerError(missing_layers));
|
||||
}
|
||||
|
||||
Ok(layered_topology)
|
||||
}
|
||||
fn mix_route(&self) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
let mut layered_topology = self.make_layered_topology()?;
|
||||
let num_layers = layered_topology.len();
|
||||
let route = (1..=num_layers as u64)
|
||||
.map(|layer| layered_topology.remove(&layer).unwrap()) // for each layer
|
||||
.map(|nodes| nodes.into_iter().choose(&mut rand::thread_rng()).unwrap()) // choose random node
|
||||
.map(|random_node| random_node.into()) // and convert it into sphinx specific node format
|
||||
.collect();
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
// sets a route to specific provider
|
||||
fn route_to(&self, provider_node: SphinxNode) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
Ok(self
|
||||
.mix_route()?
|
||||
.into_iter()
|
||||
.chain(std::iter::once(provider_node))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn all_paths(&self) -> Result<Vec<Vec<SphinxNode>>, NymTopologyError> {
|
||||
let mut layered_topology = self.make_layered_topology()?;
|
||||
let providers = self.get_mix_provider_nodes();
|
||||
|
||||
let sorted_layers: Vec<Vec<SphinxNode>> = (1..=layered_topology.len() as u64)
|
||||
.map(|layer| layered_topology.remove(&layer).unwrap()) // get all nodes per layer
|
||||
.map(|layer_nodes| layer_nodes.into_iter().map(|node| node.into()).collect()) // convert them into 'proper' sphinx nodes
|
||||
.chain(std::iter::once(
|
||||
providers.into_iter().map(|node| node.into()).collect(),
|
||||
)) // append all providers to the end
|
||||
.collect();
|
||||
|
||||
let all_paths = sorted_layers
|
||||
.into_iter()
|
||||
.multi_cartesian_product() // create all possible paths through that
|
||||
.collect();
|
||||
|
||||
Ok(all_paths)
|
||||
}
|
||||
|
||||
fn filter_node_versions(
|
||||
&self,
|
||||
mix_version: &str,
|
||||
provider_version: &str,
|
||||
coco_version: &str,
|
||||
) -> Self {
|
||||
let filtered_mixes = self
|
||||
.get_mix_nodes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|mix_node| mix_node.version == mix_version)
|
||||
.collect();
|
||||
let filtered_providers = self
|
||||
.get_mix_provider_nodes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|provider_node| provider_node.version == provider_version)
|
||||
.collect();
|
||||
let filtered_coco_nodes = self
|
||||
.get_coco_nodes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|coco_node| coco_node.version == coco_version)
|
||||
.collect();
|
||||
|
||||
Self::new_from_nodes(filtered_mixes, filtered_providers, filtered_coco_nodes)
|
||||
}
|
||||
|
||||
fn can_construct_path_through(&self) -> bool {
|
||||
match self.make_layered_topology() {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: tests...
|
||||
@@ -0,0 +1,35 @@
|
||||
# nym-mixnode Changelog
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* added separate announce address
|
||||
* allows announcing dns hostname instead of an ip address
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* Fixed crash when directory server goes down
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* cleaned up a lot of internal dependencies
|
||||
* reporting version to the directory server
|
||||
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
|
||||
* more informative error messages
|
||||
* generalised identity keys
|
||||
* generalised Topology handling
|
||||
* started slow transition to `log` crate by `nym-client`
|
||||
* start of 'MixMining'
|
||||
* start of validator node
|
||||
|
||||
## 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,26 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-mixnode"
|
||||
version = "0.3.2"
|
||||
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"
|
||||
log = "0.4.8"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
|
||||
## internal
|
||||
addressing = {path = "../common/addressing" }
|
||||
directory-client = { path = "../common/clients/directory-client" }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.2"
|
||||
@@ -0,0 +1,11 @@
|
||||
# Nym Mixnode
|
||||
|
||||
A Rust mixnode implementation.
|
||||
|
||||
## 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 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage.
|
||||
|
||||
By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option.
|
||||
@@ -0,0 +1,93 @@
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use std::process;
|
||||
|
||||
mod mix_peer;
|
||||
mod node;
|
||||
|
||||
fn main() {
|
||||
let arg_matches = App::new("Nym Mixnode")
|
||||
.version(built_info::PKG_VERSION)
|
||||
.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("announce_host")
|
||||
.long("announce-host")
|
||||
.help("The host that will be reported to the directory server")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("announce_port")
|
||||
.long("announce-port")
|
||||
.help("The port that will be reported to the directory server")
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
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,32 @@
|
||||
use addressing;
|
||||
use sphinx::route::NodeAddressBytes;
|
||||
use std::error::Error;
|
||||
use std::net::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: NodeAddressBytes) -> MixPeer {
|
||||
let next_hop_socket_address =
|
||||
addressing::socket_address_from_encoded_bytes(next_hop_address.to_bytes());
|
||||
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,95 @@
|
||||
use directory_client::metrics::MixMetric;
|
||||
use directory_client::requests::metrics_mixes_post::MetricsMixPoster;
|
||||
use directory_client::DirectoryClient;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error};
|
||||
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_client::Config,
|
||||
pub_key_str: String,
|
||||
) {
|
||||
let delay_duration = Duration::from_secs(METRICS_INTERVAL);
|
||||
let directory_client = directory_client::Client::new(cfg);
|
||||
loop {
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
let (received, sent) =
|
||||
MetricsReporter::acquire_and_reset_metrics(metrics.clone()).await;
|
||||
|
||||
match directory_client.metrics_post.post(&MixMetric {
|
||||
pub_key: pub_key_str.clone(),
|
||||
received,
|
||||
sent,
|
||||
}) {
|
||||
Err(err) => error!("failed to send metrics - {:?}", err),
|
||||
Ok(_) => debug!("sent metrics information"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
use crate::mix_peer::MixPeer;
|
||||
use crate::node;
|
||||
use crate::node::metrics::MetricsReporter;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::SinkExt;
|
||||
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;
|
||||
|
||||
mod metrics;
|
||||
mod presence;
|
||||
pub mod runner;
|
||||
|
||||
pub struct Config {
|
||||
announce_address: String,
|
||||
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 {
|
||||
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, config: node::Config) -> 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_client::Config {
|
||||
base_url: self.directory_server.clone(),
|
||||
};
|
||||
let pub_key_str =
|
||||
base64::encode_config(&self.public_key.to_bytes().to_vec(), base64::URL_SAFE);
|
||||
|
||||
rt.spawn({
|
||||
let presence_notifier = presence::Notifier::new(&config);
|
||||
presence_notifier.run()
|
||||
});
|
||||
|
||||
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,47 @@
|
||||
use crate::node;
|
||||
use directory_client::presence::MixNodePresence;
|
||||
use directory_client::requests::presence_mixnodes_post::PresenceMixNodesPoster;
|
||||
use directory_client::DirectoryClient;
|
||||
use log::{debug, error};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Notifier {
|
||||
pub net_client: directory_client::Client,
|
||||
presence: MixNodePresence,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(node_config: &node::Config) -> Notifier {
|
||||
let config = directory_client::Config {
|
||||
base_url: node_config.directory_server.clone(),
|
||||
};
|
||||
let net_client = directory_client::Client::new(config);
|
||||
let presence = MixNodePresence {
|
||||
host: node_config.announce_address.clone(),
|
||||
pub_key: node_config.public_key_string(),
|
||||
layer: node_config.layer as u64,
|
||||
last_seen: 0,
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
Notifier {
|
||||
net_client,
|
||||
presence,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self) {
|
||||
match self.net_client.presence_mix_nodes_post.post(&self.presence) {
|
||||
Err(err) => error!("failed to send presence - {:?}", err),
|
||||
Ok(_) => debug!("sent presence information"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(self) {
|
||||
let delay_duration = Duration::from_secs(5);
|
||||
|
||||
loop {
|
||||
self.notify();
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use crate::banner;
|
||||
use crate::node;
|
||||
use crate::node::MixNode;
|
||||
use clap::ArgMatches;
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
fn print_binding_warning(address: &str) {
|
||||
println!("\n##### WARNING #####");
|
||||
println!(
|
||||
"\nYou are trying to bind to {} - you might not be accessible to other nodes",
|
||||
address
|
||||
);
|
||||
println!("\n##### WARNING #####\n");
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
println!(
|
||||
"Announcing the following socket address: {}",
|
||||
config.announce_address
|
||||
);
|
||||
|
||||
let mix = MixNode::new(&config);
|
||||
mix.start(config).unwrap();
|
||||
}
|
||||
|
||||
fn new_config(matches: &ArgMatches) -> node::Config {
|
||||
let host = matches.value_of("host").unwrap();
|
||||
if host == "localhost" || host == "127.0.0.1" || host == "0.0.0.0" {
|
||||
print_binding_warning(host);
|
||||
}
|
||||
|
||||
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 announce_host = matches.value_of("announce_host").unwrap_or(host);
|
||||
let announce_port = matches
|
||||
.value_of("announce_port")
|
||||
.map(|port| port.parse::<u16>().unwrap())
|
||||
.unwrap_or(port);
|
||||
|
||||
let _ = (announce_host, announce_port)
|
||||
.to_socket_addrs()
|
||||
.expect("Failed to combine announce host and port")
|
||||
.next()
|
||||
.expect("Failed to extract the announce socket address from the iterator");
|
||||
|
||||
let announce_address = format!("{}:{}", announce_host, announce_port);
|
||||
|
||||
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,
|
||||
announce_address,
|
||||
secret_key,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# nym-client Changelog
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* allows receiving topology with dns hostname instead of an ip address
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* Version increase for consistency with `nym-mixnode` and `nym-sfw-provider`
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* cleaned up a lot of internal dependencies
|
||||
* reporting version to the directory server
|
||||
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
|
||||
* more informative error messages
|
||||
* generalised identity keys
|
||||
* generalised Topology handling
|
||||
* started slow transition to `log` crate by `nym-client`
|
||||
* start of 'MixMining'
|
||||
* start of validator node
|
||||
|
||||
## 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
|
||||
* client version number is now shown at node start
|
||||
* directory server location is now shown at node start
|
||||
* decrease default delays
|
||||
|
||||
## 0.1.0 - Initial Release
|
||||
|
||||
* The bare minimum set of features required by a Nym Client
|
||||
Generated
+2402
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-client"
|
||||
version = "0.3.2"
|
||||
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"
|
||||
dirs = "2.0.2"
|
||||
env_logger = "0.7.1"
|
||||
futures = "0.3.1"
|
||||
hex = "0.4.0"
|
||||
log = "0.4.8"
|
||||
pem = "0.7.0"
|
||||
rand = "0.7.2"
|
||||
rand_distr = "0.2.2"
|
||||
reqwest = "0.9.22"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tungstenite = "0.9.2"
|
||||
|
||||
## internal
|
||||
addressing = {path = "../common/addressing" }
|
||||
crypto = {path = "../common/crypto"}
|
||||
directory-client = { path = "../common/clients/directory-client" }
|
||||
healthcheck = { path = "../common/healthcheck" }
|
||||
mix-client = { path = "../common/clients/mix-client" }
|
||||
provider-client = { path = "../common/clients/provider-client" }
|
||||
sfw-provider-requests = { path = "../sfw-provider/sfw-provider-requests" }
|
||||
topology = {path = "../common/topology" }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
|
||||
# putting this explicitly below everything and most likely, the next time we look into it, it will already have a proper release
|
||||
tokio-tungstenite = { git = "https://github.com/dbcfd/tokio-tungstenite", rev="6dc2018cbfe8fe7ddd75ff977343086503135b38" }
|
||||
|
||||
[build-dependencies]
|
||||
built = "0.3.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,5 @@
|
||||
use built;
|
||||
|
||||
fn main() {
|
||||
built::write_built_file().expect("Failed to acquire build-time information");
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
use crate::clients::directory::presence::Topology;
|
||||
use crate::clients::mix::MixClient;
|
||||
use crate::clients::provider::ProviderClient;
|
||||
use crate::built_info;
|
||||
use crate::sockets::tcp;
|
||||
use crate::sockets::ws;
|
||||
use crate::utils;
|
||||
use crate::utils::topology::get_topology;
|
||||
use directory_client::presence::Topology;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::join;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
use futures::select;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use sfw_provider_requests::AuthToken;
|
||||
use sphinx::route::{Destination, DestinationAddressBytes};
|
||||
use sphinx::SphinxPacket;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pub mod directory;
|
||||
pub mod mix;
|
||||
pub mod provider;
|
||||
pub mod validator;
|
||||
use topology::NymTopology;
|
||||
|
||||
const LOOP_COVER_AVERAGE_DELAY: f64 = 0.5;
|
||||
// seconds
|
||||
@@ -44,16 +41,19 @@ struct MixTrafficController;
|
||||
impl MixTrafficController {
|
||||
// this was way more difficult to implement than what this code may suggest...
|
||||
async fn run(mut rx: mpsc::UnboundedReceiver<MixMessage>) {
|
||||
let mix_client = MixClient::new();
|
||||
let mix_client = mix_client::MixClient::new();
|
||||
while let Some(mix_message) = rx.next().await {
|
||||
println!(
|
||||
info!(
|
||||
"[MIX TRAFFIC CONTROL] - got a mix_message for {:?}",
|
||||
mix_message.0
|
||||
);
|
||||
let send_res = mix_client.send(mix_message.1, mix_message.0).await;
|
||||
match send_res {
|
||||
Ok(_) => println!("We successfully sent the message!"),
|
||||
Err(e) => eprintln!("We failed to send the message :( - {:?}", e),
|
||||
Ok(_) => {
|
||||
print!(".");
|
||||
io::stdout().flush().ok().expect("Could not flush stdout");
|
||||
}
|
||||
Err(e) => error!("We failed to send the message :( - {:?}", e),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl ReceivedMessagesBuffer {
|
||||
}
|
||||
|
||||
async fn add_new_messages(buf: Arc<FMutex<Self>>, msgs: Vec<Vec<u8>>) {
|
||||
println!("Adding new messages to the buffer! {:?}", msgs);
|
||||
info!("Adding new messages to the buffer! {:?}", msgs);
|
||||
let mut unlocked = buf.lock().await;
|
||||
unlocked.messages.extend(msgs);
|
||||
}
|
||||
@@ -157,7 +157,7 @@ impl NymClient {
|
||||
topology: Topology,
|
||||
) {
|
||||
loop {
|
||||
println!("[LOOP COVER TRAFFIC STREAM] - next cover message!");
|
||||
info!("[LOOP COVER TRAFFIC STREAM] - next cover message!");
|
||||
let delay = utils::poisson::sample(LOOP_COVER_AVERAGE_DELAY);
|
||||
let delay_duration = Duration::from_secs_f64(delay);
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
@@ -176,30 +176,38 @@ impl NymClient {
|
||||
topology: Topology,
|
||||
) {
|
||||
loop {
|
||||
println!("[OUT QUEUE] here I will be sending real traffic (or loop cover if nothing is available)");
|
||||
select! {
|
||||
info!("[OUT QUEUE] here I will be sending real traffic (or loop cover if nothing is available)");
|
||||
// TODO: consider replacing select macro with our own proper future definition with polling
|
||||
let traffic_message = select! {
|
||||
real_message = input_rx.next() => {
|
||||
println!("[OUT QUEUE] - we got a real message!");
|
||||
let real_message = real_message.expect("The channel must have closed! - if the client hasn't crashed, it should have!");
|
||||
info!("[OUT QUEUE] - we got a real message!");
|
||||
if real_message.is_none() {
|
||||
error!("Unexpected 'None' real message!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let real_message = real_message.unwrap();
|
||||
println!("real: {:?}", real_message);
|
||||
let encapsulated_message = utils::sphinx::encapsulate_message(real_message.0, real_message.1, &topology);
|
||||
mix_tx.send(MixMessage(encapsulated_message.0, encapsulated_message.1)).await.unwrap();
|
||||
utils::sphinx::encapsulate_message(real_message.0, real_message.1, &topology)
|
||||
},
|
||||
|
||||
default => {
|
||||
println!("[OUT QUEUE] - no real message - going to send extra loop cover");
|
||||
let cover_message = utils::sphinx::loop_cover_message(our_info.address, our_info.identifier, &topology);
|
||||
mix_tx.send(MixMessage(cover_message.0, cover_message.1)).await.unwrap();
|
||||
info!("[OUT QUEUE] - no real message - going to send extra loop cover");
|
||||
utils::sphinx::loop_cover_message(our_info.address, our_info.identifier, &topology)
|
||||
}
|
||||
};
|
||||
|
||||
mix_tx
|
||||
.send(MixMessage(traffic_message.0, traffic_message.1))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let delay_duration = Duration::from_secs_f64(MESSAGE_SENDING_AVERAGE_DELAY);
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_provider_polling(
|
||||
provider_client: ProviderClient,
|
||||
provider_client: provider_client::ProviderClient,
|
||||
mut poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
|
||||
) {
|
||||
let loop_message = &utils::sphinx::LOOP_COVER_MESSAGE_PAYLOAD.to_vec();
|
||||
@@ -207,7 +215,7 @@ impl NymClient {
|
||||
loop {
|
||||
let delay_duration = Duration::from_secs_f64(FETCH_MESSAGES_DELAY);
|
||||
tokio::time::delay_for(delay_duration).await;
|
||||
println!("[FETCH MSG] - Polling provider...");
|
||||
info!("[FETCH MSG] - Polling provider...");
|
||||
let messages = provider_client.retrieve_messages().await.unwrap();
|
||||
let good_messages = messages
|
||||
.into_iter()
|
||||
@@ -219,21 +227,66 @@ impl NymClient {
|
||||
}
|
||||
|
||||
pub fn start(self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let score_threshold = 0.0;
|
||||
println!("Starting nym client");
|
||||
let mut rt = Runtime::new()?;
|
||||
|
||||
let topology = get_topology(self.directory.clone());
|
||||
// this is temporary and assumes there exists only a single provider.
|
||||
let provider_address: SocketAddr = topology
|
||||
.mix_provider_nodes
|
||||
.first()
|
||||
.unwrap()
|
||||
.host
|
||||
.parse()
|
||||
.unwrap();
|
||||
println!("Trying to obtain valid, healthy, topology");
|
||||
let full_topology = Topology::new(self.directory.clone());
|
||||
|
||||
let mut provider_client =
|
||||
ProviderClient::new(provider_address, self.address, self.auth_token);
|
||||
// run a healthcheck to determine healthy-ish nodes:
|
||||
// this is a temporary solution as the healthcheck will eventually be moved to validators
|
||||
let healthcheck_config = healthcheck::config::HealthCheck {
|
||||
directory_server: self.directory.clone(),
|
||||
// those are literally unrelevant when running single check
|
||||
interval: 100000.0,
|
||||
resolution_timeout: 5.0,
|
||||
num_test_packets: 2,
|
||||
};
|
||||
let healthcheck = healthcheck::HealthChecker::new(healthcheck_config);
|
||||
let healthcheck_result = rt.block_on(healthcheck.do_check());
|
||||
|
||||
let healthcheck_scores = match healthcheck_result {
|
||||
Err(err) => {
|
||||
error!(
|
||||
"failed to perform healthcheck to determine healthy topology - {:?}",
|
||||
err
|
||||
);
|
||||
return Err(Box::new(err));
|
||||
}
|
||||
Ok(scores) => scores,
|
||||
};
|
||||
|
||||
let healthy_topology =
|
||||
healthcheck_scores.filter_topology_by_score(&full_topology, score_threshold);
|
||||
|
||||
// for time being assume same versioning, i.e. if client is running X.Y.Z,
|
||||
// we're expecting mixes, providers and coconodes to also be running X.Y.Z
|
||||
let versioned_healthy_topology = healthy_topology.filter_node_versions(
|
||||
built_info::PKG_VERSION,
|
||||
built_info::PKG_VERSION,
|
||||
built_info::PKG_VERSION,
|
||||
);
|
||||
|
||||
// make sure you can still send a packet through the network:
|
||||
if !versioned_healthy_topology.can_construct_path_through() {
|
||||
error!("No valid path exists in the topology");
|
||||
// TODO: replace panic with proper return type
|
||||
panic!("No valid path exists in the topology");
|
||||
}
|
||||
|
||||
// this is temporary and assumes there exists only a single provider.
|
||||
let provider_client_listener_address: SocketAddr = versioned_healthy_topology
|
||||
.get_mix_provider_nodes()
|
||||
.first()
|
||||
.expect("Could not get a provider from the supplied network topology, are you using the right directory server?")
|
||||
.client_listener;
|
||||
|
||||
let mut provider_client = provider_client::ProviderClient::new(
|
||||
provider_client_listener_address,
|
||||
self.address,
|
||||
self.auth_token,
|
||||
);
|
||||
|
||||
// registration
|
||||
rt.block_on(async {
|
||||
@@ -241,7 +294,7 @@ impl NymClient {
|
||||
None => {
|
||||
let auth_token = provider_client.register().await.unwrap();
|
||||
provider_client.update_token(auth_token);
|
||||
println!("Obtained new token! - {:?}", auth_token);
|
||||
info!("Obtained new token! - {:?}", auth_token);
|
||||
}
|
||||
Some(token) => println!("Already got the token! - {:?}", token),
|
||||
}
|
||||
@@ -270,14 +323,14 @@ impl NymClient {
|
||||
let loop_cover_traffic_future = rt.spawn(NymClient::start_loop_cover_traffic_stream(
|
||||
mix_tx.clone(),
|
||||
Destination::new(self.address, Default::default()),
|
||||
topology.clone(),
|
||||
versioned_healthy_topology.clone(),
|
||||
));
|
||||
|
||||
let out_queue_control_future = rt.spawn(NymClient::control_out_queue(
|
||||
mix_tx,
|
||||
self.input_rx,
|
||||
Destination::new(self.address, Default::default()),
|
||||
topology.clone(),
|
||||
versioned_healthy_topology.clone(),
|
||||
));
|
||||
|
||||
let provider_polling_future = rt.spawn(NymClient::start_provider_polling(
|
||||
@@ -292,7 +345,7 @@ impl NymClient {
|
||||
self.input_tx,
|
||||
received_messages_buffer_output_tx,
|
||||
self.address,
|
||||
topology,
|
||||
versioned_healthy_topology,
|
||||
));
|
||||
}
|
||||
SocketType::TCP => {
|
||||
@@ -301,7 +354,7 @@ impl NymClient {
|
||||
self.input_tx,
|
||||
received_messages_buffer_output_tx,
|
||||
self.address,
|
||||
topology,
|
||||
versioned_healthy_topology,
|
||||
));
|
||||
}
|
||||
SocketType::None => (),
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::banner;
|
||||
use crate::identity::mixnet;
|
||||
use crate::persistence::pathfinder::Pathfinder;
|
||||
use crate::persistence::pemstore::PemStore;
|
||||
use clap::ArgMatches;
|
||||
use crypto::identity::MixnetIdentityKeyPair;
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
println!("{}", banner());
|
||||
@@ -12,9 +12,9 @@ pub fn execute(matches: &ArgMatches) {
|
||||
let pathfinder = Pathfinder::new(id);
|
||||
|
||||
println!("Writing keypairs to {:?}...", pathfinder.config_dir);
|
||||
let mix_keys = mixnet::KeyPair::new();
|
||||
let mix_keys = crypto::identity::DummyMixIdentityKeyPair::new();
|
||||
let pem_store = PemStore::new(pathfinder);
|
||||
pem_store.write(mix_keys);
|
||||
pem_store.write_identity(mix_keys);
|
||||
|
||||
println!("Client configuration completed.\n\n\n")
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod init;
|
||||
pub mod run;
|
||||
pub mod tcpsocket;
|
||||
pub mod websocket;
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::banner;
|
||||
use crate::clients::{NymClient, SocketType};
|
||||
use crate::persistence::pemstore;
|
||||
use crate::sockets::tcp;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPublicKey};
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
@@ -29,11 +29,17 @@ pub fn execute(matches: &ArgMatches) {
|
||||
.next()
|
||||
.expect("Failed to extract the socket address from the iterator");
|
||||
|
||||
let keypair = pemstore::read_keypair_from_disk(id);
|
||||
let auth_token = None;
|
||||
let keypair = pemstore::read_mix_identity_keypair_from_disk(id);
|
||||
// TODO: reading auth_token from disk (if exists);
|
||||
|
||||
println!("Public key: {}", keypair.public_key.to_b64_string());
|
||||
|
||||
let mut temporary_address = [0u8; 32];
|
||||
let public_key_bytes = keypair.public_key().to_bytes();
|
||||
temporary_address.copy_from_slice(&public_key_bytes[..]);
|
||||
let auth_token = None;
|
||||
let client = NymClient::new(
|
||||
keypair.public_bytes(),
|
||||
temporary_address,
|
||||
socket_address.clone(),
|
||||
directory_server,
|
||||
auth_token,
|
||||
@@ -3,6 +3,7 @@ use crate::clients::{NymClient, SocketType};
|
||||
use crate::persistence::pemstore;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use crypto::identity::{MixnetIdentityKeyPair, MixnetIdentityPublicKey};
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
@@ -28,11 +29,17 @@ pub fn execute(matches: &ArgMatches) {
|
||||
.next()
|
||||
.expect("Failed to extract the socket address from the iterator");
|
||||
|
||||
let keypair = pemstore::read_keypair_from_disk(id);
|
||||
let keypair = pemstore::read_mix_identity_keypair_from_disk(id);
|
||||
// TODO: reading auth_token from disk (if exists);
|
||||
|
||||
println!("Public key: {}", keypair.public_key.to_b64_string());
|
||||
|
||||
let mut temporary_address = [0u8; 32];
|
||||
let public_key_bytes = keypair.public_key().to_bytes();
|
||||
temporary_address.copy_from_slice(&public_key_bytes[..]);
|
||||
let auth_token = None;
|
||||
let client = NymClient::new(
|
||||
keypair.public_bytes(),
|
||||
temporary_address,
|
||||
socket_address,
|
||||
directory_server,
|
||||
auth_token,
|
||||
@@ -1,16 +1,25 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use env_logger;
|
||||
use log::*;
|
||||
use std::process;
|
||||
|
||||
pub mod clients;
|
||||
mod commands;
|
||||
mod identity;
|
||||
mod persistence;
|
||||
mod sockets;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
pub mod built_info {
|
||||
// The file has been placed there by the build script.
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let arg_matches = App::new("Nym Client")
|
||||
.version("0.1.0")
|
||||
.version(built_info::PKG_VERSION)
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Client")
|
||||
.subcommand(
|
||||
@@ -28,22 +37,6 @@ fn main() {
|
||||
.takes_value(true)
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("run")
|
||||
.about("Run a persistent Nym client process")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to run.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("directory")
|
||||
.long("directory")
|
||||
.help("Address of the directory server the client is getting topology from")
|
||||
.takes_value(true),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("tcpsocket")
|
||||
.about("Run Nym client that listens for bytes on a TCP socket")
|
||||
@@ -94,20 +87,14 @@ fn main() {
|
||||
.get_matches();
|
||||
|
||||
if let Err(e) = execute(arg_matches) {
|
||||
println!("{}", e);
|
||||
error!("{}", 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() {
|
||||
("init", Some(m)) => Ok(commands::init::execute(m)),
|
||||
("run", Some(m)) => Ok(commands::run::execute(m)),
|
||||
("tcpsocket", Some(m)) => Ok(commands::tcpsocket::execute(m)),
|
||||
("websocket", Some(m)) => Ok(commands::websocket::execute(m)),
|
||||
_ => Err(usage()),
|
||||
@@ -0,0 +1,98 @@
|
||||
use crate::persistence::pathfinder::Pathfinder;
|
||||
use pem::{encode, parse, Pem};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn read_mix_identity_keypair_from_disk(
|
||||
id: String,
|
||||
) -> crypto::identity::DummyMixIdentityKeyPair {
|
||||
let pathfinder = Pathfinder::new(id);
|
||||
let pem_store = PemStore::new(pathfinder);
|
||||
let keypair = pem_store.read_identity();
|
||||
keypair
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn read_mix_encryption_keypair_from_disk(_id: String) -> crypto::encryption::x25519::KeyPair {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub struct PemStore {
|
||||
config_dir: PathBuf,
|
||||
private_mix_key: PathBuf,
|
||||
public_mix_key: PathBuf,
|
||||
}
|
||||
|
||||
impl PemStore {
|
||||
pub fn new(pathfinder: Pathfinder) -> PemStore {
|
||||
PemStore {
|
||||
config_dir: pathfinder.config_dir,
|
||||
private_mix_key: pathfinder.private_mix_key,
|
||||
public_mix_key: pathfinder.public_mix_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_identity<IDPair, Priv, Pub>(&self) -> IDPair
|
||||
where
|
||||
IDPair: crypto::identity::MixnetIdentityKeyPair<Priv, Pub>,
|
||||
Priv: crypto::identity::MixnetIdentityPrivateKey,
|
||||
Pub: crypto::identity::MixnetIdentityPublicKey,
|
||||
{
|
||||
let private_pem = self.read_pem_file(self.private_mix_key.clone());
|
||||
let public_pem = self.read_pem_file(self.public_mix_key.clone());
|
||||
|
||||
let key_pair = IDPair::from_bytes(&private_pem.contents, &public_pem.contents);
|
||||
|
||||
assert_eq!(key_pair.private_key().pem_type(), private_pem.tag);
|
||||
assert_eq!(key_pair.public_key().pem_type(), public_pem.tag);
|
||||
|
||||
key_pair
|
||||
}
|
||||
|
||||
fn read_pem_file(&self, filepath: PathBuf) -> Pem {
|
||||
let mut pem_bytes = File::open(filepath).expect("Could not open stored keys from disk.");
|
||||
let mut buf = Vec::new();
|
||||
pem_bytes
|
||||
.read_to_end(&mut buf)
|
||||
.expect("PEM bytes reading failed.");
|
||||
let pem = parse(&buf).expect("PEM parsing failed while reading keys");
|
||||
|
||||
pem
|
||||
}
|
||||
// This should be refactored and made more generic for when we have other kinds of
|
||||
// KeyPairs that we want to persist (e.g. validator keypairs, or keys for
|
||||
// signing vs encryption). However, for the moment, it does the job.
|
||||
pub fn write_identity<IDPair, Priv, Pub>(&self, key_pair: IDPair)
|
||||
where
|
||||
IDPair: crypto::identity::MixnetIdentityKeyPair<Priv, Pub>,
|
||||
Priv: crypto::identity::MixnetIdentityPrivateKey,
|
||||
Pub: crypto::identity::MixnetIdentityPublicKey,
|
||||
{
|
||||
std::fs::create_dir_all(self.config_dir.clone()).unwrap();
|
||||
|
||||
let private_key = key_pair.private_key();
|
||||
let public_key = key_pair.public_key();
|
||||
self.write_pem_file(
|
||||
self.private_mix_key.clone(),
|
||||
private_key.to_bytes(),
|
||||
private_key.pem_type(),
|
||||
);
|
||||
self.write_pem_file(
|
||||
self.public_mix_key.clone(),
|
||||
public_key.to_bytes(),
|
||||
public_key.pem_type(),
|
||||
);
|
||||
}
|
||||
|
||||
fn write_pem_file(&self, filepath: PathBuf, data: Vec<u8>, tag: String) {
|
||||
let pem = Pem {
|
||||
tag,
|
||||
contents: data,
|
||||
};
|
||||
let key = encode(&pem);
|
||||
|
||||
let mut file = File::create(filepath).unwrap();
|
||||
file.write_all(key.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
use crate::clients::directory::presence::Topology;
|
||||
use crate::clients::BufferResponse;
|
||||
use crate::clients::InputMessage;
|
||||
use directory_client::presence::Topology;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::future::FutureExt;
|
||||
use futures::io::Error;
|
||||
use futures::SinkExt;
|
||||
use sphinx::route::{Destination, DestinationAddressBytes};
|
||||
use std::borrow::Borrow;
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::prelude::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use std::borrow::Borrow;
|
||||
use tokio::prelude::*;
|
||||
|
||||
const SEND_REQUEST_PREFIX: u8 = 1;
|
||||
const FETCH_REQUEST_PREFIX: u8 = 2;
|
||||
@@ -42,7 +42,6 @@ impl From<io::Error> for TCPSocketError {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ClientRequest {
|
||||
Send {
|
||||
message: Vec<u8>,
|
||||
@@ -67,7 +66,7 @@ impl TryFrom<&[u8]> for ClientRequest {
|
||||
FETCH_REQUEST_PREFIX => Ok(ClientRequest::Fetch),
|
||||
GET_CLIENTS_REQUEST_PREFIX => Ok(ClientRequest::GetClients),
|
||||
OWN_DETAILS_REQUEST_PREFIX => Ok(ClientRequest::OwnDetails),
|
||||
_ => Err(UnknownRequestError)
|
||||
_ => Err(UnknownRequestError),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +94,10 @@ impl ClientRequest {
|
||||
recipient_address: DestinationAddressBytes,
|
||||
mut input_tx: mpsc::UnboundedSender<InputMessage>,
|
||||
) -> ServerResponse {
|
||||
println!("send handle. sending to: {:?}, msg: {:?}", recipient_address, msg);
|
||||
println!(
|
||||
"send handle. sending to: {:?}, msg: {:?}",
|
||||
recipient_address, msg
|
||||
);
|
||||
let dummy_surb = [0; 16];
|
||||
let input_msg = InputMessage(Destination::new(recipient_address, dummy_surb), msg);
|
||||
|
||||
@@ -124,9 +126,7 @@ impl ClientRequest {
|
||||
|
||||
let messages = messages.unwrap();
|
||||
println!("fetched {} messages", messages.len());
|
||||
ServerResponse::Fetch {
|
||||
messages,
|
||||
}
|
||||
ServerResponse::Fetch { messages }
|
||||
}
|
||||
|
||||
async fn handle_get_clients(topology: &Topology) -> ServerResponse {
|
||||
@@ -148,7 +148,6 @@ impl ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ServerResponse {
|
||||
Send,
|
||||
Fetch { messages: Vec<Vec<u8>> },
|
||||
@@ -161,10 +160,10 @@ impl Into<Vec<u8>> for ServerResponse {
|
||||
fn into(self) -> Vec<u8> {
|
||||
match self {
|
||||
ServerResponse::Send => b"ok".to_vec(),
|
||||
ServerResponse::Fetch {messages} =>encode_fetched_messages(messages),
|
||||
ServerResponse::GetClients {clients} => encode_list_of_clients(clients),
|
||||
ServerResponse::OwnDetails {address} => address,
|
||||
ServerResponse::Error { message } => message.as_bytes().to_vec()
|
||||
ServerResponse::Fetch { messages } => encode_fetched_messages(messages),
|
||||
ServerResponse::GetClients { clients } => encode_list_of_clients(clients),
|
||||
ServerResponse::OwnDetails { address } => address,
|
||||
ServerResponse::Error { message } => message.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,17 +203,26 @@ impl ServerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn handle_connection(data: &[u8], request_handling_data: RequestHandlingData) -> Result<ServerResponse, TCPSocketError> {
|
||||
async fn handle_connection(
|
||||
data: &[u8],
|
||||
request_handling_data: RequestHandlingData,
|
||||
) -> Result<ServerResponse, TCPSocketError> {
|
||||
let request = ClientRequest::try_from(data)?;
|
||||
let response = match request {
|
||||
ClientRequest::Send {
|
||||
message,
|
||||
recipient_address
|
||||
} => ClientRequest::handle_send(message, recipient_address, request_handling_data.msg_input).await,
|
||||
recipient_address,
|
||||
} => {
|
||||
ClientRequest::handle_send(message, recipient_address, request_handling_data.msg_input)
|
||||
.await
|
||||
}
|
||||
ClientRequest::Fetch => ClientRequest::handle_fetch(request_handling_data.msg_query).await,
|
||||
ClientRequest::GetClients => ClientRequest::handle_get_clients(request_handling_data.topology.borrow()).await,
|
||||
ClientRequest::OwnDetails => ClientRequest::handle_own_details(request_handling_data.self_address).await,
|
||||
ClientRequest::GetClients => {
|
||||
ClientRequest::handle_get_clients(request_handling_data.topology.borrow()).await
|
||||
}
|
||||
ClientRequest::OwnDetails => {
|
||||
ClientRequest::handle_own_details(request_handling_data.self_address).await
|
||||
}
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
@@ -262,7 +270,7 @@ async fn accept_connection(
|
||||
};
|
||||
match handle_connection(&buf[..n], request_handling_data).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => ServerResponse::new_error(format!("{:?}", e))
|
||||
Err(e) => ServerResponse::new_error(format!("{:?}", e)),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -302,4 +310,4 @@ pub async fn start_tcpsocket(
|
||||
|
||||
eprintln!("The tcpsocket went kaput...");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::clients::directory::presence::Topology;
|
||||
use crate::clients::BufferResponse;
|
||||
use crate::clients::InputMessage;
|
||||
use directory_client::presence::Topology;
|
||||
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::future::FutureExt;
|
||||
use futures::io::Error;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sphinx::route::{Destination, DestinationAddressBytes};
|
||||
use std::io;
|
||||
@@ -64,7 +65,7 @@ impl From<Message> for ClientRequest {
|
||||
Message::Close(_) => panic!("todo: handle close!"),
|
||||
_ => panic!("Other types of messages are also unsupported!"),
|
||||
};
|
||||
serde_json::from_str(&text_msg).unwrap()
|
||||
serde_json::from_str(&text_msg).expect("unable to deserialize From<Message> json")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +106,7 @@ impl ClientRequest {
|
||||
async fn handle_fetch(mut msg_query: mpsc::UnboundedSender<BufferResponse>) -> ServerResponse {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
if msg_query.send(res_tx).await.is_err() {
|
||||
warn!("Failed to handle_fetch. msg_query.send() is an error.");
|
||||
return ServerResponse::Error {
|
||||
message: "Server failed to receive messages".to_string(),
|
||||
};
|
||||
@@ -113,6 +115,7 @@ impl ClientRequest {
|
||||
let messages = res_rx.map(|msg| msg).await;
|
||||
|
||||
if messages.is_err() {
|
||||
warn!("Failed to handle_fetch. messages is an error");
|
||||
return ServerResponse::Error {
|
||||
message: "Server failed to receive messages".to_string(),
|
||||
};
|
||||
@@ -196,6 +199,7 @@ async fn accept_connection(
|
||||
self_address: DestinationAddressBytes,
|
||||
topology: Topology,
|
||||
) {
|
||||
warn!("accept_connection");
|
||||
let address = stream
|
||||
.peer_addr()
|
||||
.expect("connected streams should have a peer address");
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod addressing;
|
||||
pub mod bytes;
|
||||
pub mod poisson;
|
||||
pub mod sphinx;
|
||||
pub mod topology;
|
||||
@@ -0,0 +1,40 @@
|
||||
use addressing;
|
||||
use sphinx::route::{Destination, DestinationAddressBytes, SURBIdentifier};
|
||||
use sphinx::SphinxPacket;
|
||||
use std::net::SocketAddr;
|
||||
use topology::NymTopology;
|
||||
|
||||
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
|
||||
|
||||
pub fn loop_cover_message<T: NymTopology>(
|
||||
our_address: DestinationAddressBytes,
|
||||
surb_id: SURBIdentifier,
|
||||
topology: &T,
|
||||
) -> (SocketAddr, SphinxPacket) {
|
||||
let destination = Destination::new(our_address, surb_id);
|
||||
|
||||
encapsulate_message(destination, LOOP_COVER_MESSAGE_PAYLOAD.to_vec(), topology)
|
||||
}
|
||||
|
||||
pub fn encapsulate_message<T: NymTopology>(
|
||||
recipient: Destination,
|
||||
message: Vec<u8>,
|
||||
topology: &T,
|
||||
) -> (SocketAddr, SphinxPacket) {
|
||||
let mut providers = topology.get_mix_provider_nodes();
|
||||
let provider = providers.pop().unwrap().into();
|
||||
|
||||
let route = topology.route_to(provider).unwrap();
|
||||
|
||||
// Set average packet delay to an arbitrary but at least not super-slow value for testing.
|
||||
let average_delay = 0.1;
|
||||
let delays = sphinx::header::delays::generate(route.len(), average_delay);
|
||||
|
||||
// build the packet
|
||||
let packet = sphinx::SphinxPacket::new(message, &route[..], &recipient, &delays).unwrap();
|
||||
|
||||
let first_node_address =
|
||||
addressing::socket_address_from_encoded_bytes(route.first().unwrap().address.to_bytes());
|
||||
|
||||
(first_node_address, packet)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[healthcheck]
|
||||
|
||||
#directory-server = "http://localhost:8080"
|
||||
directory-server = "https://qa-directory.nymtech.net"
|
||||
interval = 10.0
|
||||
test-packets-per-node = 2 # in seconds
|
||||
resolution-timeout = 5 # in seconds
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
|
||||
#// Copyright 2020 The Nym Mixnet Authors
|
||||
#//
|
||||
#// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#// you may not use this file except in compliance with the License.
|
||||
#// You may obtain a copy of the License at
|
||||
#//
|
||||
#// http://www.apache.org/licenses/LICENSE-2.0
|
||||
#//
|
||||
#// Unless required by applicable law or agreed to in writing, software
|
||||
#// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#// See the License for the specific language governing permissions and
|
||||
#// limitations under the License.
|
||||
|
||||
|
||||
echo "Killing old testnet processes..."
|
||||
|
||||
killall nym-mixnode
|
||||
killall nym-sfw-provider
|
||||
|
||||
echo "Press CTRL-C to stop."
|
||||
|
||||
cargo build
|
||||
|
||||
MAX_LAYERS=3
|
||||
NUMMIXES=${1:-3} # Set $NUMMIXES to default of 3, but allow the user to set other values if desired
|
||||
|
||||
for (( j=0; j<$NUMMIXES; j++ ))
|
||||
|
||||
# Note: to disable logging (or direct it to another output) modify the constant on top of mixnode or provider;
|
||||
# Will make it later either configurable by flags or config file.
|
||||
do
|
||||
let layer=j%MAX_LAYERS+1
|
||||
$PWD/target/debug/nym-mixnode run --port $((9980+$j)) --host "localhost" --layer $layer --directory http://localhost:8080 &
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sleep 1
|
||||
$PWD/target/debug/nym-sfw-provider run --clientHost "localhost" --mixHost "localhost" --mixPort 9997 --clientPort 9998 --directory http://localhost:8080
|
||||
|
||||
# trap call ctrl_c()
|
||||
trap ctrl_c SIGINT SIGTERM SIGTSTP
|
||||
function ctrl_c() {
|
||||
echo "** Trapped SIGINT, SIGTERM and SIGTSTP"
|
||||
for (( j=0; j<$NUMMIXES; j++ ));
|
||||
do
|
||||
kill_port $((9980+$j))
|
||||
done
|
||||
}
|
||||
|
||||
function kill_port() {
|
||||
PID=$(lsof -t -i:$1)
|
||||
echo "$PID"
|
||||
kill -TERM $PID || kill -KILL $PID
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# nym-sfw-provider Changelog
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* Version increase for consistency with `nym-mixnode` and `nym-client`
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* Fixed crash when directory server goes down
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* cleaned up a lot of internal dependencies
|
||||
* reporting version to the directory server
|
||||
* printing warning on trying to bind to "localhost", "127.0.0.1" or "0.0.0.0"
|
||||
* more informative error messages
|
||||
* generalised identity keys
|
||||
* generalised Topology handling
|
||||
* started slow transition to `log` crate by `nym-client`
|
||||
* start of 'MixMining'
|
||||
* start of validator node
|
||||
|
||||
|
||||
## 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,33 @@
|
||||
[package]
|
||||
build = "build.rs"
|
||||
name = "nym-sfw-provider"
|
||||
version = "0.3.2"
|
||||
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"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.2"
|
||||
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"
|
||||
|
||||
## internal
|
||||
crypto = {path = "../common/crypto"}
|
||||
directory-client = { path = "../common/clients/directory-client" }
|
||||
sfw-provider-requests = { path = "./sfw-provider-requests" }
|
||||
|
||||
## will be moved to proper dependencies once released
|
||||
sphinx = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
|
||||
[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");
|
||||
}
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
# 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)",
|
||||
"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 = { git = "https://github.com/nymtech/sphinx", rev="1d8cefcb6a0cb8e87d00d89eb1ccf2839e92aa1f" }
|
||||
@@ -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,194 @@
|
||||
use crate::provider::ServiceProvider;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use crypto::identity::MixnetIdentityKeyPair;
|
||||
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(built_info::PKG_VERSION)
|
||||
.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)
|
||||
.required(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)
|
||||
.required(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 print_binding_warning(address: &str) {
|
||||
println!("\n##### WARNING #####");
|
||||
println!(
|
||||
"\nYou are trying to bind to {} - you might not be accessible to other nodes",
|
||||
address
|
||||
);
|
||||
println!("\n##### WARNING #####\n");
|
||||
}
|
||||
|
||||
fn run(matches: &ArgMatches) {
|
||||
println!("{}", banner());
|
||||
let config = new_config(matches);
|
||||
let provider = ServiceProvider::new(config);
|
||||
|
||||
provider.start().unwrap()
|
||||
}
|
||||
|
||||
fn new_config(matches: &ArgMatches) -> provider::Config {
|
||||
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),
|
||||
};
|
||||
|
||||
if mix_host == "localhost" || mix_host == "127.0.0.1" || mix_host == "0.0.0.0" {
|
||||
print_binding_warning(mix_host);
|
||||
}
|
||||
|
||||
if client_host == "localhost" || client_host == "127.0.0.1" || client_host == "0.0.0.0" {
|
||||
print_binding_warning(client_host);
|
||||
}
|
||||
|
||||
let key_pair = crypto::identity::DummyMixIdentityKeyPair::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!("store_dir is: {:?}", store_dir);
|
||||
println!(
|
||||
"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!("Listening for mixnet packets on {}", mix_socket_address);
|
||||
println!("Listening for client requests on {}", client_socket_address);
|
||||
|
||||
provider::Config {
|
||||
mix_socket_address,
|
||||
directory_server,
|
||||
public_key: key_pair.public_key,
|
||||
client_socket_address,
|
||||
secret_key: key_pair.private_key,
|
||||
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 crypto::identity::{DummyMixIdentityPrivateKey, MixnetIdentityPrivateKey};
|
||||
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: DummyMixIdentityPrivateKey,
|
||||
}
|
||||
|
||||
impl ClientProcessingData {
|
||||
pub(crate) fn new(
|
||||
store_dir: PathBuf,
|
||||
registered_clients_ledger: Arc<FMutex<ClientLedger>>,
|
||||
secret_key: DummyMixIdentityPrivateKey,
|
||||
) -> 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: DummyMixIdentityPrivateKey) -> 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("/tmp/");
|
||||
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 = DummyMixIdentityPrivateKey::from_bytes(&[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 = DummyMixIdentityPrivateKey::from_bytes(&[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 = DummyMixIdentityPrivateKey::from_bytes(&[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 crypto::identity::DummyMixIdentityPrivateKey;
|
||||
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: DummyMixIdentityPrivateKey,
|
||||
pub(crate) store_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl MixProcessingData {
|
||||
pub(crate) fn new(secret_key: DummyMixIdentityPrivateKey, 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.as_scalar()) {
|
||||
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,314 @@
|
||||
use crate::provider::client_handling::{ClientProcessingData, ClientRequestProcessor};
|
||||
use crate::provider::mix_handling::{MixPacketProcessor, MixProcessingData};
|
||||
use crate::provider::storage::ClientStorage;
|
||||
use crypto::identity::{DummyMixIdentityPrivateKey, DummyMixIdentityPublicKey};
|
||||
use directory_client::presence::MixProviderClient;
|
||||
use futures::io::Error;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
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: DummyMixIdentityPublicKey,
|
||||
pub secret_key: DummyMixIdentityPrivateKey,
|
||||
pub store_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[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)| base64::encode_config(v, base64::URL_SAFE))
|
||||
.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,
|
||||
public_key: DummyMixIdentityPublicKey,
|
||||
secret_key: DummyMixIdentityPrivateKey,
|
||||
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: DummyMixIdentityPrivateKey,
|
||||
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: DummyMixIdentityPrivateKey,
|
||||
) -> 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.client_network_address.clone(),
|
||||
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.clone(),
|
||||
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,70 @@
|
||||
use crate::provider::ClientLedger;
|
||||
use crypto::identity::DummyMixIdentityPublicKey;
|
||||
use directory_client::presence::MixProviderPresence;
|
||||
use directory_client::requests::presence_providers_post::PresenceMixProviderPoster;
|
||||
use directory_client::DirectoryClient;
|
||||
use futures::lock::Mutex as FMutex;
|
||||
use log::{debug, error};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Notifier {
|
||||
pub net_client: directory_client::Client,
|
||||
client_ledger: Arc<FMutex<ClientLedger>>,
|
||||
client_listener: String,
|
||||
mixnet_listener: String,
|
||||
pub_key: String,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(
|
||||
directory_server_address: String,
|
||||
client_listener: SocketAddr,
|
||||
mixnet_listener: SocketAddr,
|
||||
pub_key: DummyMixIdentityPublicKey,
|
||||
client_ledger: Arc<FMutex<ClientLedger>>,
|
||||
) -> Notifier {
|
||||
let directory_config = directory_client::Config {
|
||||
base_url: directory_server_address,
|
||||
};
|
||||
let net_client = directory_client::Client::new(directory_config);
|
||||
|
||||
Notifier {
|
||||
net_client,
|
||||
client_listener: client_listener.to_string(),
|
||||
mixnet_listener: mixnet_listener.to_string(),
|
||||
pub_key: pub_key.to_b64_string(),
|
||||
client_ledger,
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_presence(&self) -> MixProviderPresence {
|
||||
let unlocked_ledger = self.client_ledger.lock().await;
|
||||
|
||||
MixProviderPresence {
|
||||
client_listener: self.client_listener.clone(),
|
||||
mixnet_listener: self.mixnet_listener.clone(),
|
||||
pub_key: self.pub_key.clone(),
|
||||
registered_clients: unlocked_ledger.current_clients(),
|
||||
last_seen: 0,
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self, presence: MixProviderPresence) {
|
||||
match self.net_client.presence_providers_post.post(&presence) {
|
||||
Err(err) => error!("failed to send presence - {:?}", err),
|
||||
Ok(_) => debug!("sent presence information"),
|
||||
}
|
||||
}
|
||||
|
||||
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,124 @@
|
||||
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
|
||||
);
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CocoPresence {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixNodePresence {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderPresence {
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub registered_clients: Vec<MixProviderClient>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderClient {
|
||||
pub pub_key: String,
|
||||
}
|
||||
|
||||
// Topology shows us the current state of the overall Nym network
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Topology {
|
||||
pub coco_nodes: Vec<CocoPresence>,
|
||||
pub mix_nodes: Vec<MixNodePresence>,
|
||||
pub mix_provider_nodes: Vec<MixProviderPresence>,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::banner;
|
||||
//use crate::clients::NymClient;
|
||||
//use crate::persistence::pemstore;
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn execute(_matches: &ArgMatches) {
|
||||
println!("{}", banner());
|
||||
panic!("For time being this command is deprecated! Please use 'websocket' instead");
|
||||
//
|
||||
// let is_local = matches.is_present("local");
|
||||
// let id = matches.value_of("id").unwrap().to_string();
|
||||
// println!("Starting client...");
|
||||
//
|
||||
// let keypair = pemstore::read_keypair_from_disk(id);
|
||||
// let client = NymClient::new(keypair.public_bytes(), is_local);
|
||||
// client.start("127.0.0.1:9000".parse().unwrap()).unwrap();
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
|
||||
// This keypair serves as the user's identity within the Mixnet
|
||||
pub struct KeyPair {
|
||||
pub private: Scalar,
|
||||
pub public: MontgomeryPoint,
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
pub fn new() -> KeyPair {
|
||||
let (private, public) = sphinx::crypto::keygen();
|
||||
KeyPair { private, public }
|
||||
}
|
||||
|
||||
pub fn from_bytes(private_bytes: Vec<u8>, public_bytes: Vec<u8>) -> KeyPair {
|
||||
let mut bytes = [0; 32];
|
||||
bytes.copy_from_slice(&private_bytes[..]);
|
||||
let private = Scalar::from_canonical_bytes(bytes).unwrap();
|
||||
let mut bytes = [0; 32];
|
||||
bytes.copy_from_slice(&public_bytes[..]);
|
||||
let public = MontgomeryPoint(bytes);
|
||||
KeyPair { private, public }
|
||||
}
|
||||
|
||||
pub fn private_bytes(&self) -> [u8; 32] {
|
||||
self.private.to_bytes()
|
||||
}
|
||||
|
||||
pub fn public_bytes(&self) -> [u8; 32] {
|
||||
self.public.to_bytes()
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod mixnet;
|
||||
@@ -1 +0,0 @@
|
||||
// TODO types for Validator keys will go in here once we hook this up.
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod clients;
|
||||
pub mod identity;
|
||||
pub mod persistence;
|
||||
pub mod sockets;
|
||||
pub mod utils;
|
||||
@@ -1,72 +0,0 @@
|
||||
use crate::identity::mixnet::KeyPair;
|
||||
use crate::persistence::pathfinder::Pathfinder;
|
||||
use pem::{encode, parse, Pem};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn read_keypair_from_disk(id: String) -> KeyPair {
|
||||
let pathfinder = Pathfinder::new(id);
|
||||
let pem_store = PemStore::new(pathfinder);
|
||||
let keypair = pem_store.read();
|
||||
keypair
|
||||
}
|
||||
|
||||
pub struct PemStore {
|
||||
config_dir: PathBuf,
|
||||
private_mix_key: PathBuf,
|
||||
public_mix_key: PathBuf,
|
||||
}
|
||||
|
||||
impl PemStore {
|
||||
pub fn new(pathfinder: Pathfinder) -> PemStore {
|
||||
PemStore {
|
||||
config_dir: pathfinder.config_dir,
|
||||
private_mix_key: pathfinder.private_mix_key,
|
||||
public_mix_key: pathfinder.public_mix_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self) -> KeyPair {
|
||||
let private = self.read_file(self.private_mix_key.clone());
|
||||
let public = self.read_file(self.public_mix_key.clone());
|
||||
|
||||
KeyPair::from_bytes(private, public)
|
||||
}
|
||||
|
||||
fn read_file(&self, filepath: PathBuf) -> Vec<u8> {
|
||||
let mut pem_bytes = File::open(filepath).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
pem_bytes.read_to_end(&mut buf).unwrap();
|
||||
let pem = parse(&buf).unwrap();
|
||||
pem.contents
|
||||
}
|
||||
// This should be refactored and made more generic for when we have other kinds of
|
||||
// KeyPairs that we want to persist (e.g. validator keypairs, or keys for
|
||||
// signing vs encryption). However, for the moment, it does the job.
|
||||
pub fn write(&self, key_pair: KeyPair) {
|
||||
std::fs::create_dir_all(self.config_dir.clone()).unwrap();
|
||||
|
||||
self.write_pem_file(
|
||||
self.private_mix_key.clone(),
|
||||
key_pair.private_bytes(),
|
||||
String::from("SPHINX CURVE25519 PRIVATE KEY"),
|
||||
);
|
||||
self.write_pem_file(
|
||||
self.public_mix_key.clone(),
|
||||
key_pair.public_bytes(),
|
||||
String::from("SPHINX CURVE25519 PUBLIC KEY"),
|
||||
);
|
||||
}
|
||||
|
||||
fn write_pem_file(&self, filepath: PathBuf, data: [u8; 32], tag: String) {
|
||||
let pem = Pem {
|
||||
tag,
|
||||
contents: data.to_vec(),
|
||||
};
|
||||
let key = encode(&pem);
|
||||
|
||||
let mut file = File::create(filepath).unwrap();
|
||||
file.write_all(key.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use crate::clients::directory::presence::Topology;
|
||||
use crate::utils::{addressing, bytes, topology};
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use sphinx::route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier};
|
||||
use sphinx::SphinxPacket;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!";
|
||||
|
||||
pub fn loop_cover_message(
|
||||
our_address: DestinationAddressBytes,
|
||||
surb_id: SURBIdentifier,
|
||||
topology: &Topology,
|
||||
) -> (SocketAddr, SphinxPacket) {
|
||||
let destination = Destination::new(our_address, surb_id);
|
||||
|
||||
encapsulate_message(destination, LOOP_COVER_MESSAGE_PAYLOAD.to_vec(), topology)
|
||||
}
|
||||
|
||||
pub fn encapsulate_message(
|
||||
recipient: Destination,
|
||||
message: Vec<u8>,
|
||||
topology: &Topology,
|
||||
) -> (SocketAddr, SphinxPacket) {
|
||||
let mixes_route = topology::route_from(&topology);
|
||||
let first_provider = topology.mix_provider_nodes.first().unwrap();
|
||||
let decoded_key_bytes =
|
||||
base64::decode_config(&first_provider.pub_key, base64::URL_SAFE).unwrap();
|
||||
let key_bytes = bytes::zero_pad_to_32(decoded_key_bytes);
|
||||
let key = MontgomeryPoint(key_bytes);
|
||||
|
||||
let provider = Node::new(
|
||||
addressing::encoded_bytes_from_socket_address(first_provider.host.clone().parse().unwrap()),
|
||||
key,
|
||||
);
|
||||
|
||||
let route = [mixes_route, vec![provider]].concat();
|
||||
|
||||
let delays = sphinx::header::delays::generate(route.len());
|
||||
|
||||
// build the packet
|
||||
let packet = sphinx::SphinxPacket::new(message, &route[..], &recipient, &delays).unwrap();
|
||||
|
||||
let first_node_address =
|
||||
addressing::socket_address_from_encoded_bytes(route.first().unwrap().address);
|
||||
|
||||
(first_node_address, packet)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user