679 lines
16 KiB
Rust
679 lines
16 KiB
Rust
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::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<bytes::TryGetError> 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<u8> {
|
|
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<Self, Error>;
|
|
|
|
/// 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<Self, Invalid> {
|
|
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<const T: usize> Encode for &[u8; T] {
|
|
fn encode(&self, buf: &mut impl BufMut) {
|
|
buf.put_slice(&**self);
|
|
}
|
|
}
|
|
|
|
impl<const T: usize> Encode for [u8; T] {
|
|
fn encode(&self, buf: &mut impl BufMut) {
|
|
buf.put_slice(self);
|
|
}
|
|
}
|
|
|
|
impl<T> 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<T, const N: usize> Encode for BoundedVec<T, N>
|
|
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<A, B> 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<Self, Error> {
|
|
let buf: [u8; 32] = Decode::decode(buf)?;
|
|
|
|
Ok(PublicKey::from(buf))
|
|
}
|
|
}
|
|
|
|
impl Decode for Refs {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
let alias = String::decode(buf)?;
|
|
Ok(Alias::from_str(&alias).map_err(Invalid::from)?)
|
|
}
|
|
}
|
|
|
|
impl<A, B> Decode for (A, B)
|
|
where
|
|
A: Decode,
|
|
B: Decode,
|
|
{
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
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<Self, Error> {
|
|
let len = Size::decode(buf)? as usize;
|
|
|
|
if len != git::Oid::LEN_SHA1 {
|
|
return Err(Invalid::Oid { actual: len }.into());
|
|
}
|
|
|
|
Ok(git::Oid::Sha1(Decode::decode(buf)?))
|
|
}
|
|
}
|
|
|
|
impl Decode for Signature {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
let bytes: [u8; 64] = Decode::decode(buf)?;
|
|
|
|
Ok(Signature::from(bytes))
|
|
}
|
|
}
|
|
|
|
impl Decode for u8 {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
Ok(buf.try_get_u8()?)
|
|
}
|
|
}
|
|
|
|
impl Decode for u16 {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
Ok(buf.try_get_u16()?)
|
|
}
|
|
}
|
|
|
|
impl Decode for u32 {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
Ok(buf.try_get_u32()?)
|
|
}
|
|
}
|
|
|
|
impl Decode for u64 {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
Ok(buf.try_get_u64()?)
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> Decode for [u8; N] {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
let mut ary = [0; N];
|
|
buf.try_copy_to_slice(&mut ary).map_err(Error::from)?;
|
|
|
|
Ok(ary)
|
|
}
|
|
}
|
|
|
|
impl<T, const N: usize> Decode for BoundedVec<T, N>
|
|
where
|
|
T: Decode,
|
|
{
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
let features = u64::decode(buf)?;
|
|
|
|
Ok(Self::from(features))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "tor")]
|
|
impl Decode for tor::OnionAddrV3 {
|
|
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
|
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<Self, Error> {
|
|
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<Self, Error> {
|
|
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<T>(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<u8, 16>, 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: git::Oid) {
|
|
roundtrip(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<u8, 2> = vec![1, 2].try_into().unwrap();
|
|
let buf = &v.encode_to_vec();
|
|
|
|
assert_matches!(
|
|
BoundedVec::<u8, 1>::decode_exact(buf),
|
|
Err(Invalid::Bounded(crate::bounded::Error::InvalidSize {
|
|
expected: 1,
|
|
actual: 2
|
|
})),
|
|
"fail when vector is too small for buffer",
|
|
);
|
|
|
|
assert!(
|
|
BoundedVec::<u8, 2>::decode_exact(buf).is_ok(),
|
|
"successfully decode vector of same size",
|
|
);
|
|
}
|
|
}
|