diff --git a/misc/multiaddr/src/lib.rs b/misc/multiaddr/src/lib.rs index 8e50b4dc..56b46743 100644 --- a/misc/multiaddr/src/lib.rs +++ b/misc/multiaddr/src/lib.rs @@ -3,6 +3,7 @@ pub use multihash; mod protocol; +mod onion_addr; mod errors; mod from_url; @@ -26,6 +27,7 @@ use std::{ pub use self::errors::{Result, Error}; pub use self::from_url::{FromUrlErr, from_url, from_url_lossy}; pub use self::protocol::Protocol; +pub use self::onion_addr::Onion3Addr; static_assertions::const_assert! { // This check is most certainly overkill right now, but done here diff --git a/misc/multiaddr/src/onion_addr.rs b/misc/multiaddr/src/onion_addr.rs new file mode 100644 index 00000000..74b0c2d4 --- /dev/null +++ b/misc/multiaddr/src/onion_addr.rs @@ -0,0 +1,54 @@ +use std::borrow::Cow; +use std::fmt::Debug; +use serde::export::Formatter; +use std::fmt; + +/// Represents an Onion v3 address +#[derive(Clone)] +pub struct Onion3Addr<'a>(Cow<'a, [u8; 35]>, u16); + +impl<'a> Onion3Addr<'a> { + /// Return the hash of the public key as bytes + pub fn hash(&self) -> &[u8; 35] { + self.0.as_ref() + } + + /// Return the port + pub fn port(&self) -> u16 { + self.1 + } + + /// Consume this instance and create an owned version containing the same address + pub fn acquire<'b>(self) -> Onion3Addr<'b> { + Self(Cow::Owned(self.0.into_owned()), self.1) + } +} + +impl PartialEq for Onion3Addr<'_> { + fn eq(&self, other: &Self) -> bool { + self.1 == other.1 && self.0[..] == other.0[..] + } +} + +impl Eq for Onion3Addr<'_> { } + +impl From<([u8; 35], u16)> for Onion3Addr<'_> { + fn from(parts: ([u8; 35], u16)) -> Self { + Self(Cow::Owned(parts.0), parts.1) + } +} + +impl<'a> From<(&'a [u8; 35], u16)> for Onion3Addr<'a> { + fn from(parts: (&'a [u8; 35], u16)) -> Self { + Self(Cow::Borrowed(parts.0), parts.1) + } +} + +impl Debug for Onion3Addr<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("Onion3Addr") + .field(&format!("{:02x?}", &self.0[..])) + .field(&self.1) + .finish() + } +} diff --git a/misc/multiaddr/src/protocol.rs b/misc/multiaddr/src/protocol.rs index e424a5c7..0ccfc99f 100644 --- a/misc/multiaddr/src/protocol.rs +++ b/misc/multiaddr/src/protocol.rs @@ -14,6 +14,7 @@ use std::{ str::{self, FromStr} }; use unsigned_varint::{encode, decode}; +use crate::onion_addr::Onion3Addr; const DCCP: u32 = 33; const DNS4: u32 = 54; @@ -27,6 +28,7 @@ const P2P_WEBRTC_STAR: u32 = 275; const P2P_WEBSOCKET_STAR: u32 = 479; const MEMORY: u32 = 777; const ONION: u32 = 444; +const ONION3: u32 = 445; const P2P: u32 = 421; const P2P_CIRCUIT: u32 = 290; const QUIC: u32 = 460; @@ -75,6 +77,7 @@ pub enum Protocol<'a> { /// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port". Memory(u64), Onion(Cow<'a, [u8; 10]>, u16), + Onion3(Onion3Addr<'a>), P2p(Multihash), P2pCircuit, Quic, @@ -150,6 +153,11 @@ impl<'a> Protocol<'a> { .ok_or(Error::InvalidProtocolString) .and_then(|s| read_onion(&s.to_uppercase())) .map(|(a, p)| Protocol::Onion(Cow::Owned(a), p)), + "onion3" => + iter.next() + .ok_or(Error::InvalidProtocolString) + .and_then(|s| read_onion3(&s.to_uppercase())) + .map(|(a, p)| Protocol::Onion3((a, p).into())), "quic" => Ok(Protocol::Quic), "ws" => Ok(Protocol::Ws(Cow::Borrowed("/"))), "wss" => Ok(Protocol::Wss(Cow::Borrowed("/"))), @@ -242,6 +250,11 @@ impl<'a> Protocol<'a> { let port = BigEndian::read_u16(&data[10 ..]); Ok((Protocol::Onion(Cow::Borrowed(array_ref!(data, 0, 10)), port), rest)) } + ONION3 => { + let (data, rest) = split_at(37, input)?; + let port = BigEndian::read_u16(&data[35 ..]); + Ok((Protocol::Onion3((array_ref!(data, 0, 35), port).into()), rest)) + } P2P => { let (n, input) = decode::usize(input)?; let (data, rest) = split_at(n, input)?; @@ -350,6 +363,11 @@ impl<'a> Protocol<'a> { w.write_all(addr.as_ref())?; w.write_u16::(*port)? } + Protocol::Onion3(addr) => { + w.write_all(encode::u32(ONION3, &mut buf))?; + w.write_all(addr.hash().as_ref())?; + w.write_u16::(addr.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))?, @@ -397,6 +415,7 @@ impl<'a> Protocol<'a> { P2pWebSocketStar => P2pWebSocketStar, Memory(a) => Memory(a), Onion(addr, port) => Onion(Cow::Owned(addr.into_owned()), port), + Onion3(addr) => Onion3(addr.acquire()), P2p(a) => P2p(a), P2pCircuit => P2pCircuit, Quic => Quic, @@ -431,6 +450,10 @@ impl<'a> fmt::Display for Protocol<'a> { let s = BASE32.encode(addr.as_ref()); write!(f, "/onion/{}:{}", s.to_lowercase(), port) } + Onion3(addr ) => { + let s = BASE32.encode(addr.hash()); + write!(f, "/onion3/{}:{}", s.to_lowercase(), addr.port()) + } P2p(c) => write!(f, "/p2p/{}", bs58::encode(c.as_bytes()).into_string()), P2pCircuit => f.write_str("/p2p-circuit"), Quic => f.write_str("/quic"), @@ -478,34 +501,49 @@ impl<'a> From for Protocol<'a> { } } +macro_rules! read_onion_impl { + ($name:ident, $len:expr, $encoded_len:expr) => { + fn $name(s: &str) -> Result<([u8; $len], u16)> { + let mut parts = s.split(':'); + + // address part (without ".onion") + let b32 = parts.next().ok_or(Error::InvalidMultiaddr)?; + if b32.len() != $encoded_len { + return Err(Error::InvalidMultiaddr) + } + + // port number + let port = parts.next() + .ok_or(Error::InvalidMultiaddr) + .and_then(|p| str::parse(p).map_err(From::from))?; + + // port == 0 is not valid for onion + if port == 0 { + return Err(Error::InvalidMultiaddr); + } + + // nothing else expected + if parts.next().is_some() { + return Err(Error::InvalidMultiaddr) + } + + if $len != BASE32.decode_len(b32.len()).map_err(|_| Error::InvalidMultiaddr)? { + return Err(Error::InvalidMultiaddr) + } + + let mut buf = [0u8; $len]; + BASE32.decode_mut(b32.as_bytes(), &mut buf).map_err(|_| Error::InvalidMultiaddr)?; + + Ok((buf, port)) + } + } +} + // 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)) -} +read_onion_impl!(read_onion, 10, 16); +// Parse a version 3 onion address and return its binary representation. +// +// Format: ":" +read_onion_impl!(read_onion3, 35, 56); \ No newline at end of file diff --git a/misc/multiaddr/tests/lib.rs b/misc/multiaddr/tests/lib.rs index d31e45cd..545e0be2 100644 --- a/misc/multiaddr/tests/lib.rs +++ b/misc/multiaddr/tests/lib.rs @@ -76,7 +76,7 @@ struct Proto(Protocol<'static>); impl Arbitrary for Proto { fn arbitrary(g: &mut G) -> Self { use Protocol::*; - match g.gen_range(0, 23) { // TODO: Add Protocol::Quic + match g.gen_range(0, 24) { // 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))), @@ -104,7 +104,12 @@ impl Arbitrary for Proto { let mut a = [0; 10]; g.fill(&mut a); Proto(Onion(Cow::Owned(a), g.gen())) - } + }, + 23 => { + let mut a = [0; 35]; + g.fill_bytes(&mut a); + Proto(Onion3((a, g.gen()).into())) + }, _ => panic!("outside range") } } @@ -208,35 +213,54 @@ fn construct_success() { ma_valid("/ip4/127.0.0.1/tcp/9090/p2p-circuit/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", "047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", vec![Ip4(local.clone()), Tcp(9090), P2pCircuit, P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))]); + + ma_valid( + "/onion/aaimaq4ygg2iegci:80", + "BC030010C0439831B48218480050", + vec![Onion(Cow::Owned([0, 16, 192, 67, 152, 49, 180, 130, 24, 72]), 80)], + ); + ma_valid( + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234", + "BD03ADADEC040BE047F9658668B11A504F3155001F231A37F54C4476C07FB4CC139ED7E30304D2", + vec![Onion3(([173, 173, 236, 4, 11, 224, 71, 249, 101, 134, 104, 177, 26, 80, 79, 49, 85, 0, 31, 35, 26, 55, 245, 76, 68, 118, 192, 127, 180, 204, 19, 158, 215, 227, 3], 1234).into())], + ); } #[test] fn construct_fail() { - let addresses = ["/ip4", - "/ip4/::1", - "/ip4/fdpsofodsajfdoisa", - "/ip6", - "/udp", - "/tcp", - "/sctp", - "/udp/65536", - "/tcp/65536", - // "/onion/9imaq4ygg2iegci7:80", - // "/onion/aaimaq4ygg2iegci7:80", - // "/onion/timaq4ygg2iegci7:0", - // "/onion/timaq4ygg2iegci7:-1", - // "/onion/timaq4ygg2iegci7", - // "/onion/timaq4ygg2iegci@:666", - "/udp/1234/sctp", - "/udp/1234/udt/1234", - "/udp/1234/utp/1234", - "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", - "/ip4/127.0.0.1/udp", - "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", - "/ip4/127.0.0.1/tcp", - "/ip4/127.0.0.1/p2p", - "/ip4/127.0.0.1/p2p/tcp", - "/p2p-circuit/50"]; + let addresses = [ + "/ip4", + "/ip4/::1", + "/ip4/fdpsofodsajfdoisa", + "/ip6", + "/udp", + "/tcp", + "/sctp", + "/udp/65536", + "/tcp/65536", + "/onion/9imaq4ygg2iegci7:80", + "/onion/aaimaq4ygg2iegci7:80", + "/onion/timaq4ygg2iegci7:0", + "/onion/timaq4ygg2iegci7:-1", + "/onion/timaq4ygg2iegci7", + "/onion/timaq4ygg2iegci@:666", + "/onion3/9ww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd7:80", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:0", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:-1", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd", + "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyy@:666", + "/udp/1234/sctp", + "/udp/1234/udt/1234", + "/udp/1234/utp/1234", + "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/udp", + "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/tcp", + "/ip4/127.0.0.1/p2p", + "/ip4/127.0.0.1/p2p/tcp", + "/p2p-circuit/50" + ]; for address in &addresses { assert!(address.parse::().is_err(), address.to_string());