diff --git a/misc/multiaddr/Cargo.toml b/misc/multiaddr/Cargo.toml index 2b876e31..178494cc 100644 --- a/misc/multiaddr/Cargo.toml +++ b/misc/multiaddr/Cargo.toml @@ -9,8 +9,10 @@ readme = "README.md" version = "0.3.0" [dependencies] +arrayref = "0.3" bs58 = "0.2.0" byteorder = "0.4" +data-encoding = "2.1" multihash = { path = "../multihash" } serde = "1.0.70" unsigned-varint = "0.1" diff --git a/misc/multiaddr/src/lib.rs b/misc/multiaddr/src/lib.rs index 5e4d8445..e7c16266 100644 --- a/misc/multiaddr/src/lib.rs +++ b/misc/multiaddr/src/lib.rs @@ -3,8 +3,11 @@ ///! Implementation of [multiaddr](https://github.com/jbenet/multiaddr) ///! in Rust. +#[macro_use] +extern crate arrayref; extern crate bs58; extern crate byteorder; +extern crate data_encoding; extern crate serde; extern crate unsigned_varint; pub extern crate multihash; diff --git a/misc/multiaddr/src/protocol.rs b/misc/multiaddr/src/protocol.rs index a610cd4d..f12ccca5 100644 --- a/misc/multiaddr/src/protocol.rs +++ b/misc/multiaddr/src/protocol.rs @@ -1,5 +1,6 @@ use bs58; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; +use data_encoding::BASE32; use multihash::Multihash; use std::{ borrow::Cow, @@ -50,7 +51,7 @@ pub enum Protocol<'a> { P2pWebRtcStar, P2pWebSocketStar, Memory, - Onion(Cow<'a, [u8]>), + Onion(Cow<'a, [u8; 10]>, u16), P2p(Multihash), P2pCircuit, Quic, @@ -121,7 +122,11 @@ impl<'a> Protocol<'a> { } "http" => Ok(Protocol::Http), "https" => Ok(Protocol::Https), - "onion" => unimplemented!(), // FIXME + "onion" => + iter.next() + .ok_or(Error::InvalidProtocolString) + .and_then(|s| read_onion(&s.to_uppercase())) + .map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)), "quic" => Ok(Protocol::Quic), "ws" => Ok(Protocol::Ws), "wss" => Ok(Protocol::Wss), @@ -191,7 +196,11 @@ impl<'a> Protocol<'a> { P2P_WEBRTC_STAR => Ok((Protocol::P2pWebRtcStar, input)), P2P_WEBSOCKET_STAR => Ok((Protocol::P2pWebSocketStar, input)), MEMORY => Ok((Protocol::Memory, input)), - ONION => unimplemented!(), // FIXME + ONION => { + let (data, rest) = split_at(12, input)?; + let port = BigEndian::read_u16(&data[10 ..]); + Ok((Protocol::Onion(Cow::Borrowed(array_ref!(data, 0, 10)), port), rest)) + } P2P => { let (n, input) = decode::usize(input)?; let (data, rest) = split_at(n, input)?; @@ -285,7 +294,11 @@ impl<'a> Protocol<'a> { w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?; w.write_all(&bytes)? } - Protocol::Onion(_) => unimplemented!(), // FIXME + Protocol::Onion(addr, port) => { + w.write_all(encode::u32(ONION, &mut buf))?; + w.write_all(addr.as_ref())?; + w.write_u16::(*port)? + } Protocol::Quic => w.write_all(encode::u32(QUIC, &mut buf))?, Protocol::Utp => w.write_all(encode::u32(UTP, &mut buf))?, Protocol::Udt => w.write_all(encode::u32(UDT, &mut buf))?, @@ -317,7 +330,7 @@ impl<'a> Protocol<'a> { P2pWebRtcStar => P2pWebRtcStar, P2pWebSocketStar => P2pWebSocketStar, Memory => Memory, - Onion(cow) => Onion(Cow::Owned(cow.into_owned())), + Onion(addr, port) => Onion(Cow::Owned(addr.into_owned()), port), P2p(a) => P2p(a), P2pCircuit => P2pCircuit, Quic => Quic, @@ -348,7 +361,10 @@ impl<'a> fmt::Display for Protocol<'a> { P2pWebRtcStar => f.write_str("/p2p-webrtc-star"), P2pWebSocketStar => f.write_str("/p2p-websocket-star"), Memory => f.write_str("/memory"), - Onion(_) => unimplemented!(), // FIXME! + Onion(addr, port) => { + let s = BASE32.encode(addr.as_ref()); + write!(f, "/onion/{}:{}", s.to_lowercase(), port) + } P2p(c) => write!(f, "/p2p/{}", bs58::encode(c.as_bytes()).into_string()), P2pCircuit => f.write_str("/p2p-circuit"), Quic => f.write_str("/quic"), @@ -378,3 +394,35 @@ impl<'a> From for Protocol<'a> { } } +// Parse a version 2 onion address and return its binary representation. +// +// Format: ":" +fn read_onion(s: &str) -> Result<([u8; 10], u16)> { + let mut parts = s.split(':'); + + // address part (without ".onion") + let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?; + if b32.len() != 16 { + return Err(Error::InvalidMultiaddr) + } + + // port number + let port = parts.next() + .ok_or(Error::InvalidMultiaddr) + .and_then(|p| str::parse(p).map_err(From::from))?; + + // nothing else expected + if parts.next().is_some() { + return Err(Error::InvalidMultiaddr) + } + + if 10 != BASE32.decode_len(b32.len()).map_err(|_| Error::InvalidMultiaddr)? { + return Err(Error::InvalidMultiaddr) + } + + let mut buf = [0u8; 10]; + BASE32.decode_mut(b32.as_bytes(), &mut buf).map_err(|_| Error::InvalidMultiaddr)?; + + Ok((buf, port)) +} + diff --git a/misc/multiaddr/tests/lib.rs b/misc/multiaddr/tests/lib.rs index 5c7c8634..4ee5ea6b 100644 --- a/misc/multiaddr/tests/lib.rs +++ b/misc/multiaddr/tests/lib.rs @@ -59,7 +59,7 @@ struct Proto(Protocol<'static>); impl Arbitrary for Proto { fn arbitrary(g: &mut G) -> Self { use Protocol::*; - match g.gen_range(0, 22) { // TODO: Add Protocol::Quic + match g.gen_range(0, 23) { // TODO: Add Protocol::Quic 0 => Proto(Dccp(g.gen())), 1 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))), 2 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))), @@ -83,6 +83,11 @@ impl Arbitrary for Proto { 19 => Proto(Utp), 20 => Proto(Ws), 21 => Proto(Wss), + 22 => { + let mut a = [0; 10]; + g.fill(&mut a); + Proto(Onion(Cow::Owned(a), g.gen())) + } _ => panic!("outside range") } }