diff --git a/Cargo.lock b/Cargo.lock
index e29462985d..a20f860f25 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/clients/desktop/js-examples/websocket/src/index.js b/clients/desktop/js-examples/websocket/src/index.js
index 1df986bb7c..0ddbaf9c57 100644
--- a/clients/desktop/js-examples/websocket/src/index.js
+++ b/clients/desktop/js-examples/websocket/src/index.js
@@ -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
--connection-type WebSocket
on port " + port + "?");
})
connection.onmessage = function (e) {
handleResponse(e);
diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml
index fe106a7a96..a1b285d24e 100644
--- a/clients/webassembly/Cargo.toml
+++ b/clients/webassembly/Cargo.toml
@@ -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
diff --git a/clients/webassembly/js-examples/index.js b/clients/webassembly/js-examples/index.js
index a44791edcd..9be381819c 100644
--- a/clients/webassembly/js-examples/index.js
+++ b/clients/webassembly/js-examples/index.js
@@ -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();
diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs
index 2177255745..779c32c8b7 100644
--- a/clients/webassembly/src/lib.rs
+++ b/clients/webassembly/src/lib.rs
@@ -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 {
+pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) -> Vec {
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) -> Vec {
+fn payload(sphinx_packet: SphinxPacket, route: Vec) -> Vec {
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) -> Vec {
///
/// 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 {
- 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 = 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 {
+ 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 for Node {
+impl TryFrom for SphinxNode {
type Error = ();
fn try_from(node_data: NodeData) -> Result {
@@ -130,6 +119,162 @@ impl TryFrom 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
+}
diff --git a/clients/webassembly/src/models/coconodes.rs b/clients/webassembly/src/models/coconodes.rs
new file mode 100644
index 0000000000..bff8800630
--- /dev/null
+++ b/clients/webassembly/src/models/coconodes.rs
@@ -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 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 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,
+ }
+ }
+}
diff --git a/clients/webassembly/src/models/mixnodes.rs b/clients/webassembly/src/models/mixnodes.rs
new file mode 100644
index 0000000000..b1f6e7f837
--- /dev/null
+++ b/clients/webassembly/src/models/mixnodes.rs
@@ -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 for MixNodePresence {
+ type Error = io::Error;
+
+ fn try_into(self) -> Result {
+ 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 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,
+ }
+ }
+}
diff --git a/clients/webassembly/src/models/mod.rs b/clients/webassembly/src/models/mod.rs
new file mode 100644
index 0000000000..9e3c695f56
--- /dev/null
+++ b/clients/webassembly/src/models/mod.rs
@@ -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,
+ pub mix_nodes: Vec,
+ pub mix_provider_nodes: Vec,
+}
+
+impl Topology {
+ pub fn new(json: &str) -> Self {
+ serde_json::from_str(json).unwrap()
+ }
+
+ fn make_layered_topology(&self) -> Result>, NymTopologyError> {
+ let mut layered_topology: HashMap> = 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, 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, NymTopologyError> {
+ Ok(self
+ .mix_route()?
+ .into_iter()
+ .chain(std::iter::once(provider_node))
+ .collect())
+ }
+
+ pub fn mix_nodes(&self) -> Vec {
+ self.mix_nodes
+ .iter()
+ .filter_map(|x| x.clone().try_into().ok())
+ .collect()
+ }
+
+ pub fn providers(&self) -> Vec {
+ self.mix_provider_nodes
+ .iter()
+ .map(|x| x.clone().into())
+ .collect()
+ }
+}
+
+#[derive(Debug)]
+pub enum NymTopologyError {
+ InvalidMixLayerError,
+ MissingLayerError(Vec),
+}
+
+#[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_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 = mix_presence.try_into();
+ assert!(result.is_ok())
+ }
+}
diff --git a/clients/webassembly/src/models/providers.rs b/clients/webassembly/src/models/providers.rs
new file mode 100644
index 0000000000..7f01f6d6b7
--- /dev/null
+++ b/clients/webassembly/src/models/providers.rs
@@ -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,
+ pub last_seen: u64,
+ pub version: String,
+}
+
+impl Into 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 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 for MixProviderClient {
+ fn into(self) -> topology::provider::Client {
+ topology::provider::Client {
+ pub_key: self.pub_key,
+ }
+ }
+}
+
+impl From for MixProviderClient {
+ fn from(mpc: topology::provider::Client) -> Self {
+ MixProviderClient {
+ pub_key: mpc.pub_key,
+ }
+ }
+}
diff --git a/common/client-libs/directory-client/src/lib.rs b/common/client-libs/directory-client/src/lib.rs
index 6f72b3bcd7..be0f49c85a 100644
--- a/common/client-libs/directory-client/src/lib.rs
+++ b/common/client-libs/directory-client/src/lib.rs
@@ -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,