pub mod frame; pub mod message; pub mod varint; pub use frame::StreamId; pub use message::{AddressType, MessageType}; use std::convert::TryFrom; use std::fmt::Debug; use std::ops::Deref; use std::str::FromStr; use std::string::FromUtf8Error; use bytes::{Buf, BufMut}; #[cfg(feature = "i2p")] use cypheraddr::i2p; #[cfg(feature = "tor")] use cypheraddr::tor; use radicle::crypto::{PublicKey, Signature}; use radicle::git; use radicle::git::fmt; use radicle::git::raw; use radicle::identity::RepoId; use radicle::node; use radicle::node::Alias; use radicle::node::NodeId; use radicle::node::Timestamp; use radicle::node::UserAgent; use radicle::storage::refs::Refs; use radicle::storage::refs::RefsAt; use crate::bounded::BoundedVec; use crate::service::filter; /// The default type we use to represent sizes on the wire. /// /// Since wire messages are limited to 64KB by the transport layer, /// two bytes is enough to represent any message. /// /// Note that in certain cases, we may use a smaller type. pub type Size = u16; #[derive(thiserror::Error, Debug)] pub enum Invalid { #[error( "invalid Git object identifier size: expected {}, got {actual}", git::Oid::LEN_SHA1 )] Oid { actual: usize }, #[error(transparent)] Bounded(#[from] crate::bounded::Error), #[error("invalid filter size: {actual}")] FilterSize { actual: usize }, #[error("UTF-8 error: {0}")] FromUtf8(#[from] FromUtf8Error), #[error(transparent)] RefName(#[from] fmt::Error), #[error(transparent)] Alias(#[from] node::AliasError), #[error("invalid user agent string: {err}")] InvalidUserAgent { err: String }, #[cfg(feature = "tor")] #[error("invalid onion address: {0}")] OnionAddr(#[from] tor::OnionAddrDecodeError), #[cfg(feature = "i2p")] #[error("invalid i2p address: {0}")] I2pAddr(#[from] i2p::I2pAddrParseError), #[error("invalid timestamp: {actual_millis} millis")] Timestamp { actual_millis: u64 }, // Message types #[error("invalid control message type: {actual:x}")] ControlType { actual: u8 }, #[error("invalid stream type: {actual:x}")] StreamType { actual: u8 }, #[error("invalid address type: {actual:x}")] AddressType { actual: u8 }, #[error("invalid message type: {actual:x}")] MessageType { actual: u16 }, #[error("invalid info message type: {actual:x}")] InfoMessageType { actual: u16 }, // Protocol version handling #[error("invalid protocol version string: {actual:x?}")] ProtocolVersion { actual: [u8; 4] }, #[error("unsupported protocol version: {actual}")] ProtocolVersionUnsupported { actual: u8 }, } #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Invalid(#[from] Invalid), #[error( "unexpected end of buffer, requested {requested} more bytes but only {available} are available" )] UnexpectedEnd { available: usize, requested: usize }, } impl From for Error { fn from( bytes::TryGetError { available, requested, }: bytes::TryGetError, ) -> Self { Self::UnexpectedEnd { available, requested, } } } /// Things that can be encoded as binary. pub trait Encode { /// Encode self by writing it to the given buffer. fn encode(&self, buffer: &mut impl BufMut); /// A convenience wrapper around [`Encode::encode`] /// that allocates a [`Vec`]. fn encode_to_vec(&self) -> Vec { let mut buf = Vec::new(); self.encode(&mut buf); buf } } /// Things that can be decoded from binary. pub trait Decode: Sized { fn decode(buffer: &mut impl Buf) -> Result; /// A convenience wrapper around [`Decode::decode`] to decode /// from a slice exactly. /// /// # Panics /// /// - If decoding failed because there were not enough bytes. /// - If there are any bytes left after decoding. #[cfg(test)] fn decode_exact(mut data: &[u8]) -> Result { match Self::decode(&mut data) { Ok(value) => { if !data.is_empty() { panic!("{} bytes left in buffer", data.len()); } Ok(value) } Err(err @ Error::UnexpectedEnd { .. }) => { panic!("{}", err); } Err(Error::Invalid(e)) => Err(e), } } } impl Encode for u8 { fn encode(&self, buf: &mut impl BufMut) { buf.put_u8(*self); } } impl Encode for u16 { fn encode(&self, buf: &mut impl BufMut) { buf.put_u16(*self); } } impl Encode for u32 { fn encode(&self, buf: &mut impl BufMut) { buf.put_u32(*self); } } impl Encode for u64 { fn encode(&self, buf: &mut impl BufMut) { buf.put_u64(*self); } } impl Encode for PublicKey { fn encode(&self, buf: &mut impl BufMut) { self.to_byte_array().encode(buf) } } impl Encode for &[u8; T] { fn encode(&self, buf: &mut impl BufMut) { buf.put_slice(&**self); } } impl Encode for [u8; T] { fn encode(&self, buf: &mut impl BufMut) { buf.put_slice(self); } } impl Encode for &[T] where T: Encode, { fn encode(&self, buf: &mut impl BufMut) { (self.len() as Size).encode(buf); for item in self.iter() { item.encode(buf); } } } impl Encode for BoundedVec where T: Encode, { fn encode(&self, buf: &mut impl BufMut) { self.as_slice().encode(buf) } } impl Encode for &str { fn encode(&self, buf: &mut impl BufMut) { assert!(self.len() <= u8::MAX as usize); (self.len() as u8).encode(buf); let bytes = self.as_bytes(); // Nb. Don't use the [`Encode`] instance here for &[u8], because we are prefixing the // length ourselves. buf.put_slice(bytes); } } impl Encode for String { fn encode(&self, buf: &mut impl BufMut) { self.as_str().encode(buf) } } impl Encode for git::Url { fn encode(&self, buf: &mut impl BufMut) { self.to_string().encode(buf) } } impl Encode for RepoId { fn encode(&self, buf: &mut impl BufMut) { self.deref().encode(buf) } } impl Encode for Refs { fn encode(&self, buf: &mut impl BufMut) { let len: Size = self .len() .try_into() .expect("`Refs::len()` must be less than or equal to `Size::MAX`"); len.encode(buf); for (name, oid) in self.iter() { name.as_str().encode(buf); oid.encode(buf); } } } #[cfg(feature = "tor")] impl Encode for cypheraddr::tor::OnionAddrV3 { fn encode(&self, buf: &mut impl BufMut) { self.into_raw_bytes().encode(buf) } } #[cfg(feature = "i2p")] impl Encode for i2p::I2pAddr { fn encode(&self, buf: &mut impl BufMut) { self.to_string().encode(buf) } } impl Encode for UserAgent { fn encode(&self, buf: &mut impl BufMut) { self.as_ref().encode(buf) } } impl Encode for Alias { fn encode(&self, buf: &mut impl BufMut) { self.as_ref().encode(buf) } } impl Encode for (A, B) where A: Encode, B: Encode, { fn encode(&self, buf: &mut impl BufMut) { self.0.encode(buf); self.1.encode(buf); } } impl Encode for git::fmt::RefString { fn encode(&self, buf: &mut impl BufMut) { self.as_str().encode(buf) } } impl Encode for Signature { fn encode(&self, buf: &mut impl BufMut) { self.deref().encode(buf) } } impl Encode for git::Oid { fn encode(&self, buf: &mut impl BufMut) { // Nb. We use length-encoding here to support future SHA-2 object ids. let bytes: &[u8] = self.as_ref(); bytes.encode(buf) } } //////////////////////////////////////////////////////////////////////////////// impl Decode for PublicKey { fn decode(buf: &mut impl Buf) -> Result { let buf: [u8; 32] = Decode::decode(buf)?; Ok(PublicKey::from(buf)) } } impl Decode for Refs { fn decode(buf: &mut impl Buf) -> Result { let len = Size::decode(buf)?; let mut refs = Refs::new(); for _ in 0..len { let name = String::decode(buf)?; let name = git::fmt::RefString::try_from(name).map_err(Invalid::from)?; let oid = git::Oid::decode(buf)?; refs.insert(name, oid); } Ok(refs) } } impl Decode for git::fmt::RefString { fn decode(buf: &mut impl Buf) -> Result { let ref_str = String::decode(buf)?; Ok(git::fmt::RefString::try_from(ref_str).map_err(Invalid::from)?) } } impl Decode for UserAgent { fn decode(buf: &mut impl Buf) -> Result { let user_agent = String::decode(buf)?; Ok(UserAgent::from_str(&user_agent).map_err(|err| Invalid::InvalidUserAgent { err })?) } } impl Decode for Alias { fn decode(buf: &mut impl Buf) -> Result { let alias = String::decode(buf)?; Ok(Alias::from_str(&alias).map_err(Invalid::from)?) } } impl Decode for (A, B) where A: Decode, B: Decode, { fn decode(buf: &mut impl Buf) -> Result { let a = A::decode(buf)?; let b = B::decode(buf)?; Ok((a, b)) } } impl Decode for git::Oid { fn decode(buf: &mut impl Buf) -> Result { let len = Size::decode(buf)? as usize; if len != git::Oid::LEN_SHA1 { return Err(Invalid::Oid { actual: len }.into()); } let buf: [u8; git::Oid::SHA1_LEN] = Decode::decode(buf)?; let oid = raw::Oid::from_bytes(&buf).expect("the buffer is exactly the right size"); let oid = git::Oid::from(oid); Ok(oid) } } impl Decode for Signature { fn decode(buf: &mut impl Buf) -> Result { let bytes: [u8; 64] = Decode::decode(buf)?; Ok(Signature::from(bytes)) } } impl Decode for u8 { fn decode(buf: &mut impl Buf) -> Result { Ok(buf.try_get_u8()?) } } impl Decode for u16 { fn decode(buf: &mut impl Buf) -> Result { Ok(buf.try_get_u16()?) } } impl Decode for u32 { fn decode(buf: &mut impl Buf) -> Result { Ok(buf.try_get_u32()?) } } impl Decode for u64 { fn decode(buf: &mut impl Buf) -> Result { Ok(buf.try_get_u64()?) } } impl Decode for [u8; N] { fn decode(buf: &mut impl Buf) -> Result { let mut ary = [0; N]; buf.try_copy_to_slice(&mut ary).map_err(Error::from)?; Ok(ary) } } impl Decode for BoundedVec where T: Decode, { fn decode(buf: &mut impl Buf) -> Result { let len: usize = Size::decode(buf)? as usize; let mut items = Self::with_capacity(len).map_err(Invalid::from)?; for _ in 0..items.capacity() { let item = T::decode(buf)?; items.push(item).ok(); } Ok(items) } } impl Decode for String { fn decode(buf: &mut impl Buf) -> Result { let len = u8::decode(buf)?; let mut bytes = vec![0; len as usize]; buf.try_copy_to_slice(&mut bytes)?; let string = String::from_utf8(bytes).map_err(Invalid::from)?; Ok(string) } } impl Decode for RepoId { fn decode(buf: &mut impl Buf) -> Result { let oid: git::Oid = Decode::decode(buf)?; Ok(Self::from(oid)) } } impl Encode for filter::Filter { fn encode(&self, buf: &mut impl BufMut) { self.deref().as_bytes().encode(buf); } } impl Decode for filter::Filter { fn decode(buf: &mut impl Buf) -> Result { let size: usize = Size::decode(buf)? as usize; if !filter::FILTER_SIZES.contains(&size) { return Err(Invalid::FilterSize { actual: size }.into()); } let mut bytes = vec![0; size]; buf.try_copy_to_slice(&mut bytes)?; let f = filter::BloomFilter::from(bytes); debug_assert_eq!(f.hashes(), filter::FILTER_HASHES); Ok(Self::from(f)) } } impl Encode for RefsAt { fn encode(&self, buf: &mut impl BufMut) { self.remote.encode(buf); self.at.encode(buf); } } impl Decode for RefsAt { fn decode(buf: &mut impl Buf) -> Result { let remote = NodeId::decode(buf)?; let at = git::Oid::decode(buf)?; Ok(Self { remote, at }) } } impl Encode for node::Features { fn encode(&self, buf: &mut impl BufMut) { self.deref().encode(buf) } } impl Decode for node::Features { fn decode(buf: &mut impl Buf) -> Result { let features = u64::decode(buf)?; Ok(Self::from(features)) } } #[cfg(feature = "tor")] impl Decode for tor::OnionAddrV3 { fn decode(buf: &mut impl Buf) -> Result { let bytes: [u8; tor::ONION_V3_RAW_LEN] = Decode::decode(buf)?; let addr = tor::OnionAddrV3::from_raw_bytes(bytes).map_err(Invalid::from)?; Ok(addr) } } #[cfg(feature = "i2p")] impl Decode for i2p::I2pAddr { fn decode(buf: &mut impl Buf) -> Result { let s = String::decode(buf)?; let addr = i2p::I2pAddr::from_str(&s).map_err(Invalid::from)?; Ok(addr) } } impl Encode for Timestamp { fn encode(&self, buf: &mut impl BufMut) { self.deref().encode(buf) } } impl Decode for Timestamp { fn decode(buf: &mut impl Buf) -> Result { let millis = u64::decode(buf)?; let ts = Timestamp::try_from(millis).map_err(|value| Invalid::Timestamp { actual_millis: value, })?; Ok(ts) } } #[cfg(test)] fn roundtrip(value: T) where T: Encode + Decode + PartialEq + Debug, { let encoded = value.encode_to_vec(); assert_eq!(T::decode_exact(&encoded).expect("roundtrip"), value); } #[cfg(test)] #[macro_export] macro_rules! prop_roundtrip { ($t:ty, $name:tt) => { pastey::paste! { #[quickcheck] fn [< prop_roundtrip_ $name:lower >](v: $t) { $crate::wire::roundtrip(v); } } }; ($t:ty) => { pastey::paste! { prop_roundtrip!($t, [< $t >]); } }; } #[cfg(test)] mod tests { use super::*; use qcheck; use qcheck_macros::quickcheck; use radicle::assert_matches; prop_roundtrip!(u16); prop_roundtrip!(u32); prop_roundtrip!(u64); prop_roundtrip!(BoundedVec, vec); prop_roundtrip!(PublicKey); prop_roundtrip!(filter::Filter, filter); prop_roundtrip!(RepoId); prop_roundtrip!(Refs); prop_roundtrip!((String, String), tuple); #[quickcheck] fn prop_string(input: String) -> qcheck::TestResult { if input.len() > u8::MAX as usize { return qcheck::TestResult::discard(); } roundtrip(input); qcheck::TestResult::passed() } #[quickcheck] fn prop_signature(input: [u8; 64]) { roundtrip(Signature::from(input)); } #[quickcheck] fn prop_oid(input: [u8; git::Oid::SHA1_LEN]) { roundtrip(git::Oid::from_sha1(input)); } #[test] fn test_string() { assert_eq!( String::from("hello").encode_to_vec(), vec![5, b'h', b'e', b'l', b'l', b'o'] ); } #[test] fn test_alias() { assert_eq!( Alias::from_str("hello").unwrap().encode_to_vec(), vec![5, b'h', b'e', b'l', b'l', b'o'] ); } #[test] fn test_filter_invalid() { let b = bloomy::BloomFilter::with_size(filter::FILTER_SIZE_M / 3); let f = filter::Filter::from(b); let bytes = f.encode_to_vec(); assert_matches!( filter::Filter::decode_exact(&bytes).unwrap_err(), Invalid::FilterSize { .. } ); } #[test] fn test_bounded_vec_limit() { let v: BoundedVec = vec![1, 2].try_into().unwrap(); let buf = &v.encode_to_vec(); assert_matches!( BoundedVec::::decode_exact(buf), Err(Invalid::Bounded(crate::bounded::Error::InvalidSize { expected: 1, actual: 2 })), "fail when vector is too small for buffer", ); assert!( BoundedVec::::decode_exact(buf).is_ok(), "successfully decode vector of same size", ); } }