| |
pub type Size = u16;
|
| |
|
| |
#[derive(thiserror::Error, Debug)]
|
| - |
pub enum Error {
|
| + |
pub enum Invalid {
|
| + |
#[error("invalid Git object identifier size: expected {expected}, got {actual}")]
|
| + |
Oid { expected: usize, 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("invalid size: expected {expected}, got {actual}")]
|
| - |
InvalidSize { expected: usize, actual: usize },
|
| - |
#[error("invalid filter size: {0}")]
|
| - |
InvalidFilterSize(usize),
|
| - |
#[error("invalid channel type {0:x}")]
|
| - |
InvalidStreamKind(u8),
|
| |
#[error(transparent)]
|
| - |
InvalidRefName(#[from] fmt::Error),
|
| + |
RefName(#[from] fmt::Error),
|
| |
#[error(transparent)]
|
| - |
InvalidAlias(#[from] node::AliasError),
|
| - |
#[error("invalid user agent string: {0:?}")]
|
| - |
InvalidUserAgent(String),
|
| - |
#[error("invalid control message with type `{0}`")]
|
| - |
InvalidControlMessage(u8),
|
| - |
#[error("invalid protocol version header `{0:x?}`")]
|
| - |
InvalidProtocolVersion([u8; 4]),
|
| + |
Alias(#[from] node::AliasError),
|
| + |
#[error("invalid user agent string: {err}")]
|
| + |
InvalidUserAgent { err: String },
|
| |
#[error("invalid onion address: {0}")]
|
| - |
InvalidOnionAddr(#[from] tor::OnionAddrDecodeError),
|
| - |
#[error("invalid timestamp: {0}")]
|
| - |
InvalidTimestamp(u64),
|
| - |
#[error("wrong protocol version `{0}`")]
|
| - |
WrongProtocolVersion(u8),
|
| - |
#[error("unknown address type `{0}`")]
|
| - |
UnknownAddressType(u8),
|
| - |
#[error("unknown message type `{0}`")]
|
| - |
UnknownMessageType(u16),
|
| - |
#[error("unknown info type `{0}`")]
|
| - |
UnknownInfoType(u16),
|
| - |
#[error("unexpected bytes")]
|
| - |
UnexpectedBytes,
|
| + |
OnionAddr(#[from] tor::OnionAddrDecodeError),
|
| + |
#[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 },
|
| |
}
|
| |
|
| |
/// 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_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>;
|
| - |
}
|
| - |
|
| - |
/// Encode an object into a byte vector.
|
| - |
///
|
| - |
/// # Panics
|
| - |
///
|
| - |
/// If the encoded object exceeds [`Size::MAX`].
|
| - |
pub fn serialize<E: Encode + ?Sized>(data: &E) -> Vec<u8> {
|
| - |
let mut buffer = Vec::new().limit(Size::MAX as usize);
|
| - |
data.encode(&mut buffer);
|
| - |
buffer.into_inner()
|
| - |
}
|
| |
|
| - |
/// Decode an object from a slice.
|
| - |
pub fn deserialize<T: Decode>(mut data: &[u8]) -> Result<T, Error> {
|
| - |
let result = T::decode(&mut data)?;
|
| - |
|
| - |
if data.is_empty() {
|
| - |
Ok(result)
|
| - |
} else {
|
| - |
Err(Error::UnexpectedBytes)
|
| + |
/// 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 Decode for git::RefString {
|
| |
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
| |
let ref_str = String::decode(buf)?;
|
| - |
git::RefString::try_from(ref_str).map_err(Error::from)
|
| + |
Ok(git::RefString::try_from(ref_str).map_err(Invalid::from)?)
|
| |
}
|
| |
}
|
| |
|
| |
impl Decode for UserAgent {
|
| |
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
| - |
String::decode(buf).and_then(|s| UserAgent::from_str(&s).map_err(Error::InvalidUserAgent))
|
| + |
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> {
|
| - |
String::decode(buf).and_then(|s| Alias::from_str(&s).map_err(Error::from))
|
| + |
let alias = String::decode(buf)?;
|
| + |
Ok(Alias::from_str(&alias).map_err(Invalid::from)?)
|
| |
}
|
| |
}
|
| |
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_u8(input: u8) {
|
| - |
assert_eq!(deserialize::<u8>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(u8::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_u16(input: u16) {
|
| - |
assert_eq!(deserialize::<u16>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(u16::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_u32(input: u32) {
|
| - |
assert_eq!(deserialize::<u32>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(u32::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_u64(input: u64) {
|
| - |
assert_eq!(deserialize::<u64>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(u64::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
#[quickcheck]
|
| |
fn prop_vec(input: BoundedVec<String, 16>) {
|
| |
assert_eq!(
|
| - |
deserialize::<BoundedVec<String, 16>>(&serialize(&input.as_slice())).unwrap(),
|
| + |
BoundedVec::<String, 16>::decode_exact(&input.encode_vec()).unwrap(),
|
| |
input
|
| |
);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_pubkey(input: PublicKey) {
|
| - |
assert_eq!(deserialize::<PublicKey>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(PublicKey::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_filter(input: filter::Filter) {
|
| |
assert_eq!(
|
| - |
deserialize::<filter::Filter>(&serialize(&input)).unwrap(),
|
| + |
filter::Filter::decode_exact(&input.encode_vec()).unwrap(),
|
| |
input
|
| |
);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_id(input: RepoId) {
|
| - |
assert_eq!(deserialize::<RepoId>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(RepoId::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_refs(input: Refs) {
|
| - |
assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
|
| + |
assert_eq!(Refs::decode_exact(&input.encode_vec()).unwrap(), input);
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
fn prop_tuple(input: (String, String)) {
|
| |
assert_eq!(
|
| - |
deserialize::<(String, String)>(&serialize(&input)).unwrap(),
|
| + |
<(String, String)>::decode_exact(&input.encode_vec()).unwrap(),
|
| |
input
|
| |
);
|
| |
}
|
| |
fn test_filter_invalid() {
|
| |
let b = bloomy::BloomFilter::with_size(filter::FILTER_SIZE_M / 3);
|
| |
let f = filter::Filter::from(b);
|
| - |
let bytes = serialize(&f);
|
| + |
let bytes = f.encode_vec();
|
| |
|
| |
assert_matches!(
|
| - |
deserialize::<filter::Filter>(&bytes).unwrap_err(),
|
| - |
Error::InvalidFilterSize(_)
|
| + |
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 = serialize(&v);
|
| + |
let buf = &v.encode_vec();
|
| |
|
| |
assert_matches!(
|
| - |
deserialize::<BoundedVec<u8, 1>>(&buf),
|
| - |
Err(Error::InvalidSize {
|
| + |
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!(
|
| - |
deserialize::<BoundedVec<u8, 2>>(&buf).is_ok(),
|
| + |
BoundedVec::<u8, 2>::decode_exact(buf).is_ok(),
|
| |
"successfully decode vector of same size",
|
| |
);
|
| |
}
|