Skip to content

Commit 903ef44

Browse files
authored
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
1 parent 44c48ef commit 903ef44

File tree

10 files changed

+548
-70
lines changed

10 files changed

+548
-70
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/desktop/js-examples/websocket/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function main() {
88
var connection = await connectWebsocket(localClientUrl).then(function (c) {
99
return c;
1010
}).catch(function (err) {
11-
display("Websocket ERROR: " + err);
11+
display("Websocket connection error. Is the client running with <pre>--connection-type WebSocket</pre> on port " + port + "?");
1212
})
1313
connection.onmessage = function (e) {
1414
handleResponse(e);

clients/webassembly/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ crate-type = ["cdylib", "rlib"]
1111
default = ["console_error_panic_hook"]
1212

1313
[dependencies]
14+
# itertools = "0.8.2"
15+
log = "0.4"
1416
serde = { version = "1.0", features = ["derive"] }
1517
serde_json = "1.0"
1618
slice_as_array = "1.1.0"
1719
wasm-bindgen = "0.2"
20+
rand = "0.7.2"
1821

1922
# internal
2023
crypto = { path = "../../common/crypto" }
2124
nymsphinx = { path = "../../common/nymsphinx" }
22-
25+
topology = { path = "../../common/topology" }
2326

2427
# The `console_error_panic_hook` crate provides better debugging of panics by
2528
# logging them with `console.error`. This is great for development, but requires

clients/webassembly/js-examples/index.js

+1-29
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@
1414

1515
import * as wasm from "nym-sphinx-wasm";
1616

17-
class Route {
18-
constructor(nodes) {
19-
this.nodes = nodes;
20-
}
21-
}
22-
23-
class NodeData {
24-
constructor(address, public_key) {
25-
this.address = address;
26-
this.public_key = public_key;
27-
}
28-
}
29-
3017
async function main() {
3118
var gatewayUrl = "ws://127.0.0.1:1793";
3219
var directoryUrl = "http://127.0.0.1:8080/api/presence/topology";
@@ -50,10 +37,9 @@ async function main() {
5037

5138
// Create a Sphinx packet and send it to the mixnet through the Gateway node.
5239
function sendMessageToMixnet(connection, topology) {
53-
let route = constructRoute(topology);
5440
var recipient = document.getElementById("recipient").value;
5541
var sendText = document.getElementById("sendtext").value;
56-
let packet = wasm.create_sphinx_packet(JSON.stringify(route), sendText, recipient);
42+
let packet = wasm.create_sphinx_packet(JSON.stringify(topology), sendText, recipient);
5743
connection.send(packet);
5844
displaySend(packet);
5945
display("Sent a Sphinx packet containing message: " + sendText);
@@ -65,20 +51,6 @@ async function getTopology(directoryUrl) {
6551
return topology;
6652
}
6753

68-
// Construct a route from the current network topology so we can get wasm to build us a Sphinx packet
69-
function constructRoute(topology) {
70-
const mixnodes = topology.mixNodes;
71-
const provider = topology.mixProviderNodes[0];
72-
let nodes = [];
73-
mixnodes.forEach(node => {
74-
let n = new NodeData(node.host, node.pubKey);
75-
nodes.push(n);
76-
});
77-
let p = new NodeData(provider.mixnetListener, provider.pubKey)
78-
nodes.push(p);
79-
return new Route(nodes);
80-
}
81-
8254
// Let's get started!
8355
main();
8456

clients/webassembly/src/lib.rs

+173-28
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414
use crypto::identity::MixIdentityPublicKey;
15+
use models::Topology;
1516
use nymsphinx::addressing::nodes::NymNodeRoutingAddress;
1617
use nymsphinx::chunking::split_and_prepare_payloads;
18+
use nymsphinx::Node as SphinxNode;
1719
use nymsphinx::{
18-
delays, Destination, DestinationAddressBytes, Node, NodeAddressBytes, SphinxPacket,
19-
IDENTIFIER_LENGTH,
20+
delays, Destination, DestinationAddressBytes, NodeAddressBytes, SphinxPacket, IDENTIFIER_LENGTH,
2021
};
2122
use serde::{Deserialize, Serialize};
22-
use serde_json;
2323
use std::convert::TryFrom;
2424
use std::convert::TryInto;
2525
use std::net::SocketAddr;
2626
use std::time::Duration;
27+
use topology::provider::Node as TopologyNode;
2728
use wasm_bindgen::prelude::*;
2829

30+
mod models;
2931
mod utils;
3032

3133
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
@@ -53,10 +55,10 @@ pub struct NodeData {
5355
/// Message chunking is currently not implemented. If the message exceeds the
5456
/// capacity of a single Sphinx packet, the extra information will be discarded.
5557
#[wasm_bindgen]
56-
pub fn create_sphinx_packet(raw_route: &str, msg: &str, destination: &str) -> Vec<u8> {
58+
pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) -> Vec<u8> {
5759
utils::set_panic_hook(); // nicer js errors.
5860

59-
let route = sphinx_route_from(raw_route);
61+
let route = sphinx_route_from(topology_json);
6062

6163
let average_delay = Duration::from_secs_f64(0.1);
6264
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
7678
/// it should send a packet it receives. So we prepend the packet with the
7779
/// address bytes of the first mix inside the packet, so that the gateway can
7880
/// forward the packet to it.
79-
fn payload(sphinx_packet: SphinxPacket, route: Vec<Node>) -> Vec<u8> {
81+
fn payload(sphinx_packet: SphinxPacket, route: Vec<SphinxNode>) -> Vec<u8> {
8082
let packet = sphinx_packet.to_bytes();
8183
let first_mix_address = route.first().unwrap().clone().address.to_bytes().to_vec();
8284

@@ -93,29 +95,16 @@ fn payload(sphinx_packet: SphinxPacket, route: Vec<Node>) -> Vec<u8> {
9395
///
9496
/// This function panics if the supplied `raw_route` json string can't be
9597
/// extracted to a `JsonRoute`.
96-
///
97-
/// # Panics
98-
///
99-
/// This function panics if `JsonRoute.nodes` doesn't contain at least 1
100-
/// node.
101-
///
102-
fn sphinx_route_from(raw_route: &str) -> Vec<Node> {
103-
let json_route: JsonRoute = serde_json::from_str(raw_route).unwrap();
104-
105-
assert!(
106-
json_route.nodes.len() > 0,
107-
"Sphinx packet must route to at least one mixnode."
108-
);
109-
110-
let mut sphinx_route: Vec<Node> = vec![];
111-
for node_data in json_route.nodes.iter() {
112-
let x = Node::try_from(node_data.clone()).expect("Malformed NodeData");
113-
sphinx_route.push(x);
114-
}
115-
sphinx_route
98+
fn sphinx_route_from(topology_json: &str) -> Vec<SphinxNode> {
99+
let topology = Topology::new(topology_json);
100+
let p: TopologyNode = topology.providers().first().unwrap().to_owned();
101+
let provider = p.into();
102+
let route = topology.route_to(provider).unwrap();
103+
assert_eq!(4, route.len());
104+
route
116105
}
117106

118-
impl TryFrom<NodeData> for Node {
107+
impl TryFrom<NodeData> for SphinxNode {
119108
type Error = ();
120109

121110
fn try_from(node_data: NodeData) -> Result<Self, Self::Error> {
@@ -130,6 +119,162 @@ impl TryFrom<NodeData> for Node {
130119
nymsphinx::key::new(dest)
131120
};
132121

133-
Ok(Node { address, pub_key })
122+
Ok(SphinxNode { address, pub_key })
134123
}
135124
}
125+
126+
#[cfg(test)]
127+
mod test_constructing_a_sphinx_packet {
128+
use super::*;
129+
#[test]
130+
fn produces_1404_bytes() {
131+
// 32 byte address + 1372 byte sphinx packet
132+
let packet = create_sphinx_packet(
133+
topology_fixture(),
134+
"foomp",
135+
"AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
136+
);
137+
assert_eq!(1404, packet.len());
138+
}
139+
140+
#[test]
141+
fn starts_with_a_mix_address() {
142+
let mut payload = create_sphinx_packet(
143+
topology_fixture(),
144+
"foomp",
145+
"AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
146+
);
147+
let mut address_buffer = [0; 32];
148+
let _ = payload.split_off(32);
149+
address_buffer.copy_from_slice(payload.as_slice());
150+
let address = NymNodeRoutingAddress::try_from_bytes(&address_buffer);
151+
152+
assert!(address.is_ok());
153+
}
154+
}
155+
156+
#[cfg(test)]
157+
mod building_a_topology_from_json {
158+
use super::*;
159+
160+
#[test]
161+
#[should_panic]
162+
fn panics_on_empty_string() {
163+
sphinx_route_from("");
164+
}
165+
166+
#[test]
167+
#[should_panic]
168+
fn panics_on_bad_json() {
169+
sphinx_route_from("bad bad bad not json");
170+
}
171+
172+
#[test]
173+
#[should_panic]
174+
fn panics_when_there_are_no_mixnodes() {
175+
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
176+
topology.mix_nodes = vec![];
177+
let json = serde_json::to_string(&topology).unwrap();
178+
sphinx_route_from(&json);
179+
}
180+
181+
#[test]
182+
#[should_panic]
183+
fn panics_when_there_are_not_enough_mixnodes() {
184+
let mut topology: Topology = serde_json::from_str(topology_fixture()).unwrap();
185+
let node = topology.mix_nodes.first().unwrap().clone();
186+
topology.mix_nodes = vec![node]; // 1 mixnode isn't enough. Panic!
187+
let json = serde_json::to_string(&topology).unwrap();
188+
sphinx_route_from(&json);
189+
}
190+
191+
#[test]
192+
fn test_works_on_happy_json() {
193+
let route = sphinx_route_from(topology_fixture());
194+
assert_eq!(4, route.len());
195+
}
196+
}
197+
198+
#[cfg(test)]
199+
fn topology_fixture() -> &'static str {
200+
let json = r#"
201+
{
202+
"cocoNodes": [],
203+
"mixNodes": [
204+
{
205+
"host": "nym.300baud.de:1789",
206+
"pubKey": "AetTDvynUNB2N35rvCVDxkPR593Cx4PCe4QQKrMgm5RR",
207+
"version": "0.6.0",
208+
"location": "Falkenstein, DE",
209+
"layer": 3,
210+
"lastSeen": 1587572945877713700
211+
},
212+
{
213+
"host": "testnet_nymmixnode.roussel-zeter.eu:1789",
214+
"pubKey": "9wJ3zLoyat41e4ZgT1AWeueExv5c6uwnjvkRepj8Ebis",
215+
"version": "0.6.0",
216+
"location": "Geneva, CH",
217+
"layer": 3,
218+
"lastSeen": 1587572945907250400
219+
},
220+
{
221+
"host": "185.144.83.134:1789",
222+
"pubKey": "59tCzpCYsiKXz89rtvNiEYwQDdkseSShPEkifQXhsCgA",
223+
"version": "0.6.0",
224+
"location": "Bucharest",
225+
"layer": 1,
226+
"lastSeen": 1587572946007431400
227+
},
228+
{
229+
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
230+
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
231+
"version": "0.6.0",
232+
"location": "Glarus",
233+
"layer": 1,
234+
"lastSeen": 1587572945920982000
235+
},
236+
{
237+
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
238+
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
239+
"version": "0.6.0",
240+
"location": "Glarus",
241+
"layer": 2,
242+
"lastSeen": 1587572945920982000
243+
},
244+
{
245+
"host": "[2a0a:e5c0:2:2:0:c8ff:fe68:bf6b]:1789",
246+
"pubKey": "J9f9uS1hN8iwcN2STqH55fPRYqt7McEPyhNzpTYsxNdG",
247+
"version": "0.6.0",
248+
"location": "Glarus",
249+
"layer": 2,
250+
"lastSeen": 1587572945920982000
251+
}
252+
],
253+
"mixProviderNodes": [
254+
{
255+
"clientListener": "139.162.246.48:9000",
256+
"mixnetListener": "139.162.246.48:1789",
257+
"pubKey": "7vhgER4Gz789QHNTSu4apMpTcpTuUaRiLxJnbz1g2HFh",
258+
"version": "0.6.0",
259+
"location": "London, UK",
260+
"registeredClients": [
261+
{
262+
"pubKey": "5pgrc4gPHP2tBQgfezcdJ2ZAjipoAsy6evrqHdxBbVXq"
263+
}
264+
],
265+
"lastSeen": 1587572946261865200
266+
},
267+
{
268+
"clientListener": "127.0.0.1:9000",
269+
"mixnetListener": "127.0.0.1:1789",
270+
"pubKey": "2XK8RDcUTRcJLUWoDfoXc2uP4YViscMLEM5NSzhSi87M",
271+
"version": "0.6.0",
272+
"location": "unknown",
273+
"registeredClients": [],
274+
"lastSeen": 1587572946304564700
275+
}
276+
]
277+
}
278+
"#;
279+
json
280+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2020 Nym Technologies SA
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use serde::{Deserialize, Serialize};
16+
use topology::coco;
17+
18+
#[derive(Clone, Debug, Deserialize, Serialize)]
19+
#[serde(rename_all = "camelCase")]
20+
pub struct CocoPresence {
21+
pub location: String,
22+
pub host: String,
23+
pub pub_key: String,
24+
pub last_seen: u64,
25+
pub version: String,
26+
}
27+
28+
impl Into<topology::coco::Node> for CocoPresence {
29+
fn into(self) -> topology::coco::Node {
30+
topology::coco::Node {
31+
location: self.location,
32+
host: self.host,
33+
pub_key: self.pub_key,
34+
last_seen: self.last_seen,
35+
version: self.version,
36+
}
37+
}
38+
}
39+
40+
impl From<topology::coco::Node> for CocoPresence {
41+
fn from(cn: coco::Node) -> Self {
42+
CocoPresence {
43+
location: cn.location,
44+
host: cn.host,
45+
pub_key: cn.pub_key,
46+
last_seen: cn.last_seen,
47+
version: cn.version,
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)