mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-24 07:11:38 +00:00
Added Onion3 support to multiaddr (#1354)
This commit is contained in:
@ -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
|
||||
|
54
misc/multiaddr/src/onion_addr.rs
Normal file
54
misc/multiaddr/src/onion_addr.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
@ -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::<BigEndian>(*port)?
|
||||
}
|
||||
Protocol::Onion3(addr) => {
|
||||
w.write_all(encode::u32(ONION3, &mut buf))?;
|
||||
w.write_all(addr.hash().as_ref())?;
|
||||
w.write_u16::<BigEndian>(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<Ipv6Addr> 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: <base-32 address> ":" <port number>
|
||||
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: <base-32 address> ":" <port number>
|
||||
read_onion_impl!(read_onion3, 35, 56);
|
@ -76,7 +76,7 @@ struct Proto(Protocol<'static>);
|
||||
impl Arbitrary for Proto {
|
||||
fn arbitrary<G: Gen>(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::<Multiaddr>().is_err(), address.to_string());
|
||||
|
Reference in New Issue
Block a user