Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/route from topology #201

Merged
merged 4 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion clients/desktop/js-examples/websocket/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion clients/webassembly/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 1 addition & 29 deletions clients/webassembly/js-examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand All @@ -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();

Expand Down
201 changes: 173 additions & 28 deletions clients/webassembly/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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();

Expand All @@ -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> {
Expand All @@ -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
}
50 changes: 50 additions & 0 deletions clients/webassembly/src/models/coconodes.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
Loading