Skip to content

Commit

Permalink
Support and Onion3 Protocol
Browse files Browse the repository at this point in the history
Signed-off-by: Eval EXEC <[email protected]>
  • Loading branch information
eval-exec committed Dec 3, 2024
1 parent 6e26063 commit db2a98a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 1 deletion.
3 changes: 3 additions & 0 deletions multiaddr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ bytes = "1.0"
bs58 = "0.5.0"
sha2 = "0.10.0"
serde = "1"
byteorder = "1.5.0"
data-encoding = "2.6.0"
arrayref = "0.3.9"

[dev-dependencies]
parity-multiaddr = "0.11"
2 changes: 2 additions & 0 deletions multiaddr/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use unsigned_varint::decode;
pub enum Error {
DataLessThanLen,
InvalidMultiaddr,
InvalidOnion3Addr,
InvalidProtocolString,
InvalidUvar(decode::Error),
ParsingError(Box<dyn error::Error + Send + Sync>),
Expand All @@ -19,6 +20,7 @@ impl fmt::Display for Error {
Error::DataLessThanLen => f.write_str("we have less data than indicated by length"),
Error::InvalidMultiaddr => f.write_str("invalid multiaddr"),
Error::InvalidProtocolString => f.write_str("invalid protocol string"),
Error::InvalidOnion3Addr => f.write_str("invalid onion3 address"),
Error::InvalidUvar(e) => write!(f, "failed to decode unsigned varint: {}", e),
Error::ParsingError(e) => write!(f, "failed to parse: {}", e),
Error::UnknownHash => write!(f, "unknown hash"),
Expand Down
2 changes: 2 additions & 0 deletions multiaddr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
///! Mini Implementation of [multiaddr](https://github.com/jbenet/multiaddr) in Rust.
mod error;
mod onion_addr;
mod protocol;

pub use self::error::Error;
pub use self::onion_addr::Onion3Addr;
pub use self::protocol::Protocol;
use bytes::{Bytes, BytesMut};
use serde::{
Expand Down
51 changes: 51 additions & 0 deletions multiaddr/src/onion_addr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::{borrow::Cow, 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> {
Onion3Addr(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 fmt::Debug for Onion3Addr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_tuple("Onion3Addr")
.field(&format!("{:02x?}", &self.0[..]))
.field(&self.1)
.finish()
}
}
88 changes: 87 additions & 1 deletion multiaddr/src/protocol.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use arrayref::array_ref;
use byteorder::{BigEndian, ByteOrder};
use bytes::{Buf, BufMut};
use data_encoding::BASE32;
use std::{
borrow::Cow,
fmt,
Expand All @@ -7,7 +10,7 @@ use std::{
str::{self, FromStr},
};

use crate::error::Error;
use crate::{error::Error, Onion3Addr};

const DNS4: u32 = 0x36;
const DNS6: u32 = 0x37;
Expand All @@ -19,6 +22,7 @@ const TLS: u32 = 0x01c0;
const WS: u32 = 0x01dd;
const WSS: u32 = 0x01de;
const MEMORY: u32 = 0x0309;
const ONION3: u32 = 0x01bd;

const SHA256_CODE: u16 = 0x12;
const SHA256_SIZE: u8 = 32;
Expand All @@ -37,6 +41,7 @@ pub enum Protocol<'a> {
Wss,
/// Contains the "port" to contact. Similar to TCP or UDP, 0 means "assign me a port".
Memory(u64),
Onion3(Onion3Addr<'a>),
}

impl<'a> Protocol<'a> {
Expand Down Expand Up @@ -87,6 +92,11 @@ impl<'a> Protocol<'a> {
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
Ok(Protocol::Memory(s.parse()?))
}
"onion3" => iter
.next()
.ok_or(Error::InvalidProtocolString)
.and_then(|s| read_onion3(&s.to_uppercase()))
.map(|(a, p)| Protocol::Onion3((a, p).into())),
_ => Err(Error::UnknownProtocolString),
}
}
Expand All @@ -101,6 +111,14 @@ impl<'a> Protocol<'a> {
}
Ok(input.split_at(n))
}

fn split_at(n: usize, input: &[u8]) -> Result<(&[u8], &[u8]), Error> {
if input.len() < n {
return Err(Error::DataLessThanLen);
}
Ok(input.split_at(n))
}

let (id, input) = decode::u32(input)?;
match id {
DNS4 => {
Expand Down Expand Up @@ -160,6 +178,14 @@ impl<'a> Protocol<'a> {
let num = rdr.get_u64();
Ok((Protocol::Memory(num), 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,
))
}
_ => Err(Error::UnknownProtocolId(id)),
}
}
Expand Down Expand Up @@ -213,6 +239,11 @@ impl<'a> Protocol<'a> {
w.put(encode::u32(MEMORY, &mut buf));
w.put_u64(*port)
}
Protocol::Onion3(addr) => {
w.put(encode::u32(ONION3, &mut buf));
w.put(addr.hash().as_ref());
w.put_u16(addr.port());
}
}
}

Expand All @@ -229,6 +260,7 @@ impl<'a> Protocol<'a> {
Protocol::Ws => Protocol::Ws,
Protocol::Wss => Protocol::Wss,
Protocol::Memory(a) => Protocol::Memory(a),
Protocol::Onion3(addr) => Protocol::Onion3(addr.acquire()),
}
}
}
Expand All @@ -247,6 +279,10 @@ impl<'a> fmt::Display for Protocol<'a> {
Ws => write!(f, "/ws"),
Wss => write!(f, "/wss"),
Memory(port) => write!(f, "/memory/{}", port),
Onion3(addr) => {
let s = BASE32.encode(addr.hash());
write!(f, "/{}:{}", s.to_lowercase(), addr.port())
}
}
}
}
Expand Down Expand Up @@ -291,3 +327,53 @@ fn check_p2p(data: &[u8]) -> Result<(), Error> {
}
Ok(())
}

macro_rules! read_onion_impl {
($name:ident, $len:expr, $encoded_len:expr) => {
fn $name(s: &str) -> Result<([u8; $len], u16), Error> {
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 3 onion address and return its binary representation.
//
// Format: <base-32 address> ":" <port number>
read_onion_impl!(read_onion3, 35, 56);

0 comments on commit db2a98a

Please sign in to comment.