Feature/route from topology (#201)
* directory-client: removing explicit type definitions * clients/webassembly: ugly commit, removes dependency on HTTP crates ...at the cost of copying quite a bit of NymTopology-related stuff into the WebAssembly client. I'll make an issue to refactor that later. * A more helpful hint in the error message when websocket connection fails. * Removing superfluous JavaScript, route checking now happens in Rust
This commit is contained in:
Generated
+3
@@ -2678,10 +2678,13 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"crypto",
|
||||
"log 0.4.8",
|
||||
"nymsphinx",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slice_as_array",
|
||||
"topology",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"wee_alloc",
|
||||
|
||||
@@ -8,7 +8,7 @@ async function main() {
|
||||
var connection = await connectWebsocket(localClientUrl).then(function (c) {
|
||||
return c;
|
||||
}).catch(function (err) {
|
||||
display("Websocket ERROR: " + err);
|
||||
display("Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port " + port + "?");
|
||||
})
|
||||
connection.onmessage = function (e) {
|
||||
handleResponse(e);
|
||||
|
||||
@@ -11,15 +11,18 @@ crate-type = ["cdylib", "rlib"]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
# itertools = "0.8.2"
|
||||
log = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
slice_as_array = "1.1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
rand = "0.7.2"
|
||||
|
||||
# internal
|
||||
crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
|
||||
topology = { path = "../../common/topology" }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
|
||||
@@ -14,19 +14,6 @@
|
||||
|
||||
import * as wasm from "nym-sphinx-wasm";
|
||||
|
||||
class Route {
|
||||
constructor(nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
}
|
||||
|
||||
class NodeData {
|
||||
constructor(address, public_key) {
|
||||
this.address = address;
|
||||
this.public_key = public_key;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
var gatewayUrl = "ws://127.0.0.1:1793";
|
||||
var directoryUrl = "http://127.0.0.1:8080/api/presence/topology";
|
||||
@@ -50,10 +37,9 @@ async function main() {
|
||||
|
||||
// Create a Sphinx packet and send it to the mixnet through the Gateway node.
|
||||
function sendMessageToMixnet(connection, topology) {
|
||||
let route = constructRoute(topology);
|
||||
var recipient = document.getElementById("recipient").value;
|
||||
var sendText = document.getElementById("sendtext").value;
|
||||
let packet = wasm.create_sphinx_packet(JSON.stringify(route), sendText, recipient);
|
||||
let packet = wasm.create_sphinx_packet(JSON.stringify(topology), sendText, recipient);
|
||||
connection.send(packet);
|
||||
displaySend(packet);
|
||||
display("Sent a Sphinx packet containing message: " + sendText);
|
||||
@@ -65,20 +51,6 @@ async function getTopology(directoryUrl) {
|
||||
return topology;
|
||||
}
|
||||
|
||||
// Construct a route from the current network topology so we can get wasm to build us a Sphinx packet
|
||||
function constructRoute(topology) {
|
||||
const mixnodes = topology.mixNodes;
|
||||
const provider = topology.mixProviderNodes[0];
|
||||
let nodes = [];
|
||||
mixnodes.forEach(node => {
|
||||
let n = new NodeData(node.host, node.pubKey);
|
||||
nodes.push(n);
|
||||
});
|
||||
let p = new NodeData(provider.mixnetListener, provider.pubKey)
|
||||
nodes.push(p);
|
||||
return new Route(nodes);
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
|
||||
|
||||
+173
-28
@@ -12,20 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use crypto::identity::MixIdentityPublicKey;
|
||||
use models::Topology;
|
||||
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nymsphinx::chunking::split_and_prepare_payloads;
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use nymsphinx::{
|
||||
delays, Destination, DestinationAddressBytes, Node, NodeAddressBytes, SphinxPacket,
|
||||
IDENTIFIER_LENGTH,
|
||||
delays, Destination, DestinationAddressBytes, NodeAddressBytes, SphinxPacket, IDENTIFIER_LENGTH,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use topology::provider::Node as TopologyNode;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod models;
|
||||
mod utils;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
@@ -53,10 +55,10 @@ pub struct NodeData {
|
||||
/// Message chunking is currently not implemented. If the message exceeds the
|
||||
/// capacity of a single Sphinx packet, the extra information will be discarded.
|
||||
#[wasm_bindgen]
|
||||
pub fn create_sphinx_packet(raw_route: &str, msg: &str, destination: &str) -> Vec<u8> {
|
||||
pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) -> Vec<u8> {
|
||||
utils::set_panic_hook(); // nicer js errors.
|
||||
|
||||
let route = sphinx_route_from(raw_route);
|
||||
let route = sphinx_route_from(topology_json);
|
||||
|
||||
let average_delay = Duration::from_secs_f64(0.1);
|
||||
let delays = delays::generate_from_average_duration(route.len(), average_delay);
|
||||
@@ -76,7 +78,7 @@ pub fn create_sphinx_packet(raw_route: &str, msg: &str, destination: &str) -> Ve
|
||||
/// it should send a packet it receives. So we prepend the packet with the
|
||||
/// address bytes of the first mix inside the packet, so that the gateway can
|
||||
/// forward the packet to it.
|
||||
fn payload(sphinx_packet: SphinxPacket, route: Vec<Node>) -> Vec<u8> {
|
||||
fn payload(sphinx_packet: SphinxPacket, route: Vec<SphinxNode>) -> Vec<u8> {
|
||||
let packet = sphinx_packet.to_bytes();
|
||||
let first_mix_address = route.first().unwrap().clone().address.to_bytes().to_vec();
|
||||
|
||||
@@ -93,29 +95,16 @@ fn payload(sphinx_packet: SphinxPacket, route: Vec<Node>) -> Vec<u8> {
|
||||
///
|
||||
/// This function panics if the supplied `raw_route` json string can't be
|
||||
/// extracted to a `JsonRoute`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `JsonRoute.nodes` doesn't contain at least 1
|
||||
/// node.
|
||||
///
|
||||
fn sphinx_route_from(raw_route: &str) -> Vec<Node> {
|
||||
let json_route: JsonRoute = serde_json::from_str(raw_route).unwrap();
|
||||
|
||||
assert!(
|
||||
json_route.nodes.len() > 0,
|
||||
"Sphinx packet must route to at least one mixnode."
|
||||
);
|
||||
|
||||
let mut sphinx_route: Vec<Node> = vec![];
|
||||
for node_data in json_route.nodes.iter() {
|
||||
let x = Node::try_from(node_data.clone()).expect("Malformed NodeData");
|
||||
sphinx_route.push(x);
|
||||
}
|
||||
sphinx_route
|
||||
fn sphinx_route_from(topology_json: &str) -> Vec<SphinxNode> {
|
||||
let topology = Topology::new(topology_json);
|
||||
let p: TopologyNode = topology.providers().first().unwrap().to_owned();
|
||||
let provider = p.into();
|
||||
let route = topology.route_to(provider).unwrap();
|
||||
assert_eq!(4, route.len());
|
||||
route
|
||||
}
|
||||
|
||||
impl TryFrom<NodeData> for Node {
|
||||
impl TryFrom<NodeData> for SphinxNode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(node_data: NodeData) -> Result<Self, Self::Error> {
|
||||
@@ -130,6 +119,162 @@ impl TryFrom<NodeData> for Node {
|
||||
nymsphinx::key::new(dest)
|
||||
};
|
||||
|
||||
Ok(Node { address, pub_key })
|
||||
Ok(SphinxNode { address, pub_key })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_constructing_a_sphinx_packet {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn produces_1404_bytes() {
|
||||
// 32 byte address + 1372 byte sphinx packet
|
||||
let packet = create_sphinx_packet(
|
||||
topology_fixture(),
|
||||
"foomp",
|
||||
"AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
|
||||
);
|
||||
assert_eq!(1404, packet.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starts_with_a_mix_address() {
|
||||
let mut payload = create_sphinx_packet(
|
||||
topology_fixture(),
|
||||
"foomp",
|
||||
"AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
|
||||
);
|
||||
let mut address_buffer = [0; 32];
|
||||
let _ = payload.split_off(32);
|
||||
address_buffer.copy_from_slice(payload.as_slice());
|
||||
let address = NymNodeRoutingAddress::try_from_bytes(&address_buffer);
|
||||
|
||||
assert!(address.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod building_a_topology_from_json {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_on_empty_string() {
|
||||
sphinx_route_from("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_on_bad_json() {
|
||||
sphinx_route_from("bad bad bad not json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_when_there_are_no_mixnodes() {
|
||||
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
|
||||
topology.mix_nodes = vec![];
|
||||
let json = serde_json::to_string(&topology).unwrap();
|
||||
sphinx_route_from(&json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_when_there_are_not_enough_mixnodes() {
|
||||
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
|
||||
let node = topology.mix_nodes.first().unwrap().clone();
|
||||
topology.mix_nodes = vec![node]; // 1 mixnode isn't enough. Panic!
|
||||
let json = serde_json::to_string(&topology).unwrap();
|
||||
sphinx_route_from(&json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_works_on_happy_json() {
|
||||
let route = sphinx_route_from(topology_fixture());
|
||||
assert_eq!(4, route.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn topology_fixture() -> &'static str {
|
||||
let json = r#"
|
||||
{
|
||||
"cocoNodes": [],
|
||||
"mixNodes": [
|
||||
{
|
||||
"host": "nym.300baud.de:1789",
|
||||
"pubKey": "AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
|
||||
"version": "0.6.0",
|
||||
"location": "Falkenstein, DE",
|
||||
"layer": 3,
|
||||
"lastSeen": 1587572945877713700
|
||||
},
|
||||
{
|
||||
"host": "testnet_nymmixnode.roussel-zeter.eu:1789",
|
||||
"pubKey": "9wJ3zLoyat41e4ZgT1AWeueExv5c6uwnjvkRepj8Ebis",
|
||||
"version": "0.6.0",
|
||||
"location": "Geneva, CH",
|
||||
"layer": 3,
|
||||
"lastSeen": 1587572945907250400
|
||||
},
|
||||
{
|
||||
"host": "185.144.83.134:1789",
|
||||
"pubKey": "59tCzpCYsiKXz89rtvNiEYwQDdkseSShPEkifQXhsCgA",
|
||||
"version": "0.6.0",
|
||||
"location": "Bucharest",
|
||||
"layer": 1,
|
||||
"lastSeen": 1587572946007431400
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 1,
|
||||
"lastSeen": 1587572945920982000
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 2,
|
||||
"lastSeen": 1587572945920982000
|
||||
},
|
||||
{
|
||||
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
|
||||
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
|
||||
"version": "0.6.0",
|
||||
"location": "Glarus",
|
||||
"layer": 2,
|
||||
"lastSeen": 1587572945920982000
|
||||
}
|
||||
],
|
||||
"mixProviderNodes": [
|
||||
{
|
||||
"clientListener": "139.162.246.48:9000",
|
||||
"mixnetListener": "139.162.246.48:1789",
|
||||
"pubKey": "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
|
||||
"version": "0.6.0",
|
||||
"location": "London, UK",
|
||||
"registeredClients": [
|
||||
{
|
||||
"pubKey": "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"
|
||||
}
|
||||
],
|
||||
"lastSeen": 1587572946261865200
|
||||
},
|
||||
{
|
||||
"clientListener": "127.0.0.1:9000",
|
||||
"mixnetListener": "127.0.0.1:1789",
|
||||
"pubKey": "2XK8RDcUTRcJLUWoDfoXc2uP4YViscMLEM5NSzhSi87M",
|
||||
"version": "0.6.0",
|
||||
"location": "unknown",
|
||||
"registeredClients": [],
|
||||
"lastSeen": 1587572946304564700
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::coco;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CocoPresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Into<topology::coco::Node> for CocoPresence {
|
||||
fn into(self) -> topology::coco::Node {
|
||||
topology::coco::Node {
|
||||
location: self.location,
|
||||
host: self.host,
|
||||
pub_key: self.pub_key,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::coco::Node> for CocoPresence {
|
||||
fn from(cn: coco::Node) -> Self {
|
||||
CocoPresence {
|
||||
location: cn.location,
|
||||
host: cn.host,
|
||||
pub_key: cn.pub_key,
|
||||
last_seen: cn.last_seen,
|
||||
version: cn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use topology::mix;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixNodePresence {
|
||||
pub location: String,
|
||||
pub host: String,
|
||||
pub pub_key: String,
|
||||
pub layer: u64,
|
||||
pub last_seen: u64,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl TryInto<topology::mix::Node> for MixNodePresence {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_into(self) -> Result<topology::mix::Node, 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::mix::Node {
|
||||
location: self.location,
|
||||
host: resolved_hostname.unwrap(),
|
||||
pub_key: self.pub_key,
|
||||
layer: self.layer,
|
||||
last_seen: self.last_seen,
|
||||
version: self.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::mix::Node> for MixNodePresence {
|
||||
fn from(mn: mix::Node) -> Self {
|
||||
MixNodePresence {
|
||||
location: mn.location,
|
||||
host: mn.host.to_string(),
|
||||
pub_key: mn.pub_key,
|
||||
layer: mn.layer,
|
||||
last_seen: mn.last_seen,
|
||||
version: mn.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use nymsphinx::Node as SphinxNode;
|
||||
use rand::seq::IteratorRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use topology::mix;
|
||||
use topology::provider;
|
||||
|
||||
pub mod coconodes;
|
||||
pub mod mixnodes;
|
||||
pub mod providers;
|
||||
|
||||
// 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<coconodes::CocoPresence>,
|
||||
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
|
||||
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
|
||||
}
|
||||
|
||||
impl Topology {
|
||||
pub fn new(json: &str) -> Self {
|
||||
serde_json::from_str(json).unwrap()
|
||||
}
|
||||
|
||||
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<mix::Node>>, NymTopologyError> {
|
||||
let mut layered_topology: HashMap<u64, Vec<mix::Node>> = HashMap::new();
|
||||
let mut highest_layer = 0;
|
||||
for mix in self.mix_nodes() {
|
||||
// we need to have extra space for provider
|
||||
if mix.layer > nymsphinx::MAX_PATH_LENGTH as u64 {
|
||||
return Err(NymTopologyError::InvalidMixLayerError);
|
||||
}
|
||||
highest_layer = max(highest_layer, mix.layer);
|
||||
|
||||
let layer_nodes = layered_topology.entry(mix.layer).or_insert_with(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].is_empty() {
|
||||
missing_layers.push(layer);
|
||||
}
|
||||
}
|
||||
|
||||
if !missing_layers.is_empty() {
|
||||
return Err(NymTopologyError::MissingLayerError(missing_layers));
|
||||
}
|
||||
|
||||
Ok(layered_topology)
|
||||
}
|
||||
|
||||
// Tries to get a route through the mix network
|
||||
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)
|
||||
// unwrap is safe for 'remove' as it it failed, it implied the entry never existed
|
||||
// in the map in the first place which would contradict what we've just done
|
||||
.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 up a route to a specific provider
|
||||
pub fn route_to(&self, provider_node: SphinxNode) -> Result<Vec<SphinxNode>, NymTopologyError> {
|
||||
Ok(self
|
||||
.mix_route()?
|
||||
.into_iter()
|
||||
.chain(std::iter::once(provider_node))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn mix_nodes(&self) -> Vec<mix::Node> {
|
||||
self.mix_nodes
|
||||
.iter()
|
||||
.filter_map(|x| x.clone().try_into().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn providers(&self) -> Vec<provider::Node> {
|
||||
self.mix_provider_nodes
|
||||
.iter()
|
||||
.map(|x| x.clone().into())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NymTopologyError {
|
||||
InvalidMixLayerError,
|
||||
MissingLayerError(Vec<u64>),
|
||||
}
|
||||
|
||||
#[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 = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: unresolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let _result: Result<mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
// assert!(result.is_err()) // This fails only for me. Why?
|
||||
// ¯\_(ツ)_/¯ - works on my machine (and travis)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_returns_resolved_ip_on_resolvable_hostname() {
|
||||
let resolvable_hostname = "nymtech.net:1234";
|
||||
|
||||
let mix_presence = mixnodes::MixNodePresence {
|
||||
location: "".to_string(),
|
||||
host: resolvable_hostname.to_string(),
|
||||
pub_key: "".to_string(),
|
||||
layer: 0,
|
||||
last_seen: 0,
|
||||
version: "".to_string(),
|
||||
};
|
||||
|
||||
let result: Result<topology::mix::Node, std::io::Error> = mix_presence.try_into();
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topology::provider;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MixProviderPresence {
|
||||
pub location: String,
|
||||
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::provider::Node> for MixProviderPresence {
|
||||
fn into(self) -> topology::provider::Node {
|
||||
topology::provider::Node {
|
||||
location: self.location,
|
||||
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::provider::Node> for MixProviderPresence {
|
||||
fn from(mpn: provider::Node) -> Self {
|
||||
MixProviderPresence {
|
||||
location: mpn.location,
|
||||
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::provider::Client> for MixProviderClient {
|
||||
fn into(self) -> topology::provider::Client {
|
||||
topology::provider::Client {
|
||||
pub_key: self.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::provider::Client> for MixProviderClient {
|
||||
fn from(mpc: topology::provider::Client) -> Self {
|
||||
MixProviderClient {
|
||||
pub_key: mpc.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,17 +58,13 @@ pub struct Client {
|
||||
|
||||
impl DirectoryClient for Client {
|
||||
fn new(config: Config) -> Client {
|
||||
let health_check: HealthCheckRequest = HealthCheckRequest::new(config.base_url.clone());
|
||||
let metrics_mixes: MetricsMixRequest = MetricsMixRequest::new(config.base_url.clone());
|
||||
let metrics_post: MetricsMixPost = MetricsMixPost::new(config.base_url.clone());
|
||||
let presence_topology: PresenceTopologyRequest =
|
||||
PresenceTopologyRequest::new(config.base_url.clone());
|
||||
let presence_coconodes_post: PresenceCocoNodesPost =
|
||||
PresenceCocoNodesPost::new(config.base_url.clone());
|
||||
let presence_mix_nodes_post: PresenceMixNodesPost =
|
||||
PresenceMixNodesPost::new(config.base_url.clone());
|
||||
let presence_providers_post: PresenceProvidersPost =
|
||||
PresenceProvidersPost::new(config.base_url);
|
||||
let health_check = HealthCheckRequest::new(config.base_url.clone());
|
||||
let metrics_mixes = MetricsMixRequest::new(config.base_url.clone());
|
||||
let metrics_post = MetricsMixPost::new(config.base_url.clone());
|
||||
let presence_topology = PresenceTopologyRequest::new(config.base_url.clone());
|
||||
let presence_coconodes_post = PresenceCocoNodesPost::new(config.base_url.clone());
|
||||
let presence_mix_nodes_post = PresenceMixNodesPost::new(config.base_url.clone());
|
||||
let presence_providers_post = PresenceProvidersPost::new(config.base_url);
|
||||
Client {
|
||||
health_check,
|
||||
metrics_mixes,
|
||||
|
||||
Reference in New Issue
Block a user