735 lines
21 KiB
Rust
735 lines
21 KiB
Rust
pub mod message;
|
|
pub mod transcode;
|
|
|
|
use std::collections::{BTreeMap, HashMap, VecDeque};
|
|
use std::convert::TryFrom;
|
|
use std::net;
|
|
use std::ops::Deref;
|
|
use std::string::FromUtf8Error;
|
|
use std::{io, mem};
|
|
|
|
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
|
|
use nakamoto_net as nakamoto;
|
|
use nakamoto_net::{Link, LocalTime};
|
|
|
|
use crate::address;
|
|
use crate::crypto::{PublicKey, Signature, Signer, Unverified};
|
|
use crate::deserializer::Deserializer;
|
|
use crate::git;
|
|
use crate::git::fmt;
|
|
use crate::hash::Digest;
|
|
use crate::identity::Id;
|
|
use crate::node;
|
|
use crate::service;
|
|
use crate::service::reactor::Io;
|
|
use crate::service::{filter, routing, session};
|
|
use crate::storage::refs::Refs;
|
|
use crate::storage::refs::SignedRefs;
|
|
use crate::storage::WriteStorage;
|
|
use crate::wire::transcode::{Framer, Handshake, HandshakeResult, MuxMsg, Transcode};
|
|
|
|
/// 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 Error {
|
|
#[error("i/o: {0}")]
|
|
Io(#[from] io::Error),
|
|
#[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(transparent)]
|
|
InvalidRefName(#[from] fmt::Error),
|
|
#[error("unknown address type `{0}`")]
|
|
UnknownAddressType(u8),
|
|
#[error("unknown message type `{0}`")]
|
|
UnknownMessageType(u16),
|
|
}
|
|
|
|
impl Error {
|
|
/// Whether we've reached the end of file. This will be true when we fail to decode
|
|
/// a message because there's not enough data in the stream.
|
|
pub fn is_eof(&self) -> bool {
|
|
matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
|
|
}
|
|
}
|
|
|
|
/// Things that can be encoded as binary.
|
|
pub trait Encode {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error>;
|
|
}
|
|
|
|
/// Things that can be decoded from binary.
|
|
pub trait Decode: Sized {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error>;
|
|
}
|
|
|
|
/// Encode an object into a vector.
|
|
pub fn serialize<T: Encode + ?Sized>(data: &T) -> Vec<u8> {
|
|
let mut buffer = Vec::new();
|
|
let len = data
|
|
.encode(&mut buffer)
|
|
.expect("in-memory writes don't error");
|
|
|
|
debug_assert_eq!(len, buffer.len());
|
|
|
|
buffer
|
|
}
|
|
|
|
/// Decode an object from a vector.
|
|
pub fn deserialize<T: Decode>(data: &[u8]) -> Result<T, Error> {
|
|
let mut cursor = io::Cursor::new(data);
|
|
|
|
T::decode(&mut cursor)
|
|
}
|
|
|
|
impl Encode for u8 {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
writer.write_u8(*self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl Encode for u16 {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
writer.write_u16::<NetworkEndian>(*self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl Encode for u32 {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
writer.write_u32::<NetworkEndian>(*self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl Encode for u64 {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
writer.write_u64::<NetworkEndian>(*self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl Encode for PublicKey {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.deref().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl<const T: usize> Encode for &[u8; T] {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
// TODO: This can be removed when the clippy bugs are fixed
|
|
#[allow(clippy::explicit_auto_deref)]
|
|
writer.write_all(*self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl<const T: usize> Encode for [u8; T] {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
writer.write_all(self)?;
|
|
|
|
Ok(mem::size_of::<Self>())
|
|
}
|
|
}
|
|
|
|
impl<T> Encode for &[T]
|
|
where
|
|
T: Encode,
|
|
{
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = (self.len() as Size).encode(writer)?;
|
|
|
|
for item in self.iter() {
|
|
n += item.encode(writer)?;
|
|
}
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl Encode for &str {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
assert!(self.len() <= u8::MAX as usize);
|
|
|
|
let n = (self.len() as u8).encode(writer)?;
|
|
let bytes = self.as_bytes();
|
|
|
|
// Nb. Don't use the [`Encode`] instance here for &[u8], because we are prefixing the
|
|
// length ourselves.
|
|
writer.write_all(bytes)?;
|
|
|
|
Ok(n + bytes.len())
|
|
}
|
|
}
|
|
|
|
impl Encode for String {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.as_str().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Encode for git::Url {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.to_string().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Encode for Digest {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.as_ref().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Encode for Id {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.deref().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Encode for Refs {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let len: Size = self
|
|
.len()
|
|
.try_into()
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
|
let mut n = len.encode(writer)?;
|
|
|
|
for (name, oid) in self.iter() {
|
|
n += name.as_str().encode(writer)?;
|
|
n += oid.encode(writer)?;
|
|
}
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl Encode for Signature {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.deref().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Encode for git::Oid {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
// Nb. We use length-encoding here to support future SHA-2 object ids.
|
|
self.as_bytes().encode(writer)
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
impl Decode for PublicKey {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let buf: [u8; 32] = Decode::decode(reader)?;
|
|
|
|
PublicKey::try_from(buf)
|
|
.map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())))
|
|
}
|
|
}
|
|
|
|
impl Decode for Refs {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let len = Size::decode(reader)?;
|
|
let mut refs = BTreeMap::new();
|
|
|
|
for _ in 0..len {
|
|
let name = String::decode(reader)?;
|
|
let name = git::RefString::try_from(name).map_err(Error::from)?;
|
|
let oid = git::Oid::decode(reader)?;
|
|
|
|
refs.insert(name, oid);
|
|
}
|
|
Ok(refs.into())
|
|
}
|
|
}
|
|
|
|
impl Decode for git::Oid {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let len = Size::decode(reader)? as usize;
|
|
#[allow(non_upper_case_globals)]
|
|
const expected: usize = mem::size_of::<git::raw::Oid>();
|
|
|
|
if len != expected {
|
|
return Err(Error::InvalidSize {
|
|
expected,
|
|
actual: len,
|
|
});
|
|
}
|
|
|
|
let buf: [u8; expected] = Decode::decode(reader)?;
|
|
let oid = git::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<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let bytes: [u8; 64] = Decode::decode(reader)?;
|
|
|
|
Ok(Signature::from(bytes))
|
|
}
|
|
}
|
|
|
|
impl Decode for u8 {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
reader.read_u8().map_err(Error::from)
|
|
}
|
|
}
|
|
|
|
impl Decode for u16 {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
reader.read_u16::<NetworkEndian>().map_err(Error::from)
|
|
}
|
|
}
|
|
|
|
impl Decode for u32 {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
reader.read_u32::<NetworkEndian>().map_err(Error::from)
|
|
}
|
|
}
|
|
|
|
impl Decode for u64 {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
reader.read_u64::<NetworkEndian>().map_err(Error::from)
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> Decode for [u8; N] {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let mut ary = [0; N];
|
|
reader.read_exact(&mut ary)?;
|
|
|
|
Ok(ary)
|
|
}
|
|
}
|
|
|
|
impl<T> Decode for Vec<T>
|
|
where
|
|
T: Decode,
|
|
{
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let len: Size = Size::decode(reader)?;
|
|
let mut vec = Vec::with_capacity(len as usize);
|
|
|
|
for _ in 0..len {
|
|
let item = T::decode(reader)?;
|
|
vec.push(item);
|
|
}
|
|
Ok(vec)
|
|
}
|
|
}
|
|
|
|
impl Decode for String {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let len = u8::decode(reader)?;
|
|
let mut bytes = vec![0; len as usize];
|
|
|
|
reader.read_exact(&mut bytes)?;
|
|
|
|
let string = String::from_utf8(bytes)?;
|
|
|
|
Ok(string)
|
|
}
|
|
}
|
|
|
|
impl Decode for Id {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let oid: git::Oid = Decode::decode(reader)?;
|
|
|
|
Ok(Self::from(oid))
|
|
}
|
|
}
|
|
|
|
impl Decode for Digest {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let bytes: [u8; 32] = Decode::decode(reader)?;
|
|
|
|
Ok(Self::from(bytes))
|
|
}
|
|
}
|
|
|
|
impl Encode for filter::Filter {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = 0;
|
|
|
|
n += self.deref().as_bytes().encode(writer)?;
|
|
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl Decode for filter::Filter {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let size: usize = Size::decode(reader)? as usize;
|
|
if !filter::FILTER_SIZES.contains(&size) {
|
|
return Err(Error::InvalidFilterSize(size));
|
|
}
|
|
|
|
let mut bytes = vec![0; size];
|
|
reader.read_exact(&mut bytes[..])?;
|
|
|
|
let f = filter::BloomFilter::from(bytes);
|
|
debug_assert_eq!(f.hashes(), filter::FILTER_HASHES);
|
|
|
|
Ok(Self::from(f))
|
|
}
|
|
}
|
|
|
|
impl<V> Encode for SignedRefs<V> {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = 0;
|
|
|
|
n += self.refs.encode(writer)?;
|
|
n += self.signature.encode(writer)?;
|
|
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl Decode for SignedRefs<Unverified> {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let refs = Refs::decode(reader)?;
|
|
let signature = Signature::decode(reader)?;
|
|
|
|
Ok(Self::new(refs, signature))
|
|
}
|
|
}
|
|
|
|
impl Encode for node::Features {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
self.deref().encode(writer)
|
|
}
|
|
}
|
|
|
|
impl Decode for node::Features {
|
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
|
let features = u64::decode(reader)?;
|
|
|
|
Ok(Self::from(features))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Inbox<T: Transcode> {
|
|
pub pipeline: Framer<T>,
|
|
pub deserializer: Deserializer,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Wire<R, S, W, G, H: Handshake> {
|
|
handshakes: HashMap<net::SocketAddr, H>,
|
|
inner_queue: VecDeque<nakamoto::Io<service::Event, service::DisconnectReason>>,
|
|
inboxes: HashMap<net::SocketAddr, Inbox<H::Transcoder>>,
|
|
inner: service::Service<R, S, W, G>,
|
|
}
|
|
|
|
impl<R, S, W, G, H: Handshake> Wire<R, S, W, G, H> {
|
|
pub fn new(inner: service::Service<R, S, W, G>) -> Self {
|
|
Self {
|
|
handshakes: HashMap::new(),
|
|
inner_queue: Default::default(),
|
|
inboxes: HashMap::new(),
|
|
inner,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R, S, W, G, H> nakamoto::Protocol for Wire<R, S, W, G, H>
|
|
where
|
|
R: routing::Store,
|
|
S: address::Store,
|
|
W: WriteStorage + 'static,
|
|
G: Signer,
|
|
H: Handshake,
|
|
{
|
|
type Event = service::Event;
|
|
type Command = service::Command;
|
|
type DisconnectReason = service::DisconnectReason;
|
|
|
|
fn initialize(&mut self, time: LocalTime) {
|
|
self.inner.initialize(time)
|
|
}
|
|
|
|
fn tick(&mut self, now: nakamoto::LocalTime) {
|
|
self.inner.tick(now)
|
|
}
|
|
|
|
fn wake(&mut self) {
|
|
self.inner.wake()
|
|
}
|
|
|
|
fn command(&mut self, cmd: Self::Command) {
|
|
self.inner.command(cmd)
|
|
}
|
|
|
|
fn attempted(&mut self, addr: &std::net::SocketAddr) {
|
|
self.inner.attempted(addr)
|
|
}
|
|
|
|
fn connected(&mut self, addr: net::SocketAddr, local_addr: &net::SocketAddr, link: Link) {
|
|
self.handshakes.insert(addr, H::new(link));
|
|
self.inner.connecting(addr, local_addr, link)
|
|
}
|
|
|
|
fn disconnected(
|
|
&mut self,
|
|
addr: &net::SocketAddr,
|
|
reason: nakamoto::DisconnectReason<service::DisconnectReason>,
|
|
) {
|
|
self.handshakes.remove(addr);
|
|
self.inboxes.remove(addr);
|
|
self.inner.disconnected(addr, &reason)
|
|
}
|
|
|
|
fn received_bytes(&mut self, addr: &net::SocketAddr, raw_bytes: &[u8]) {
|
|
if let Some(handshake) = self.handshakes.remove(addr) {
|
|
debug_assert!(!self.inboxes.contains_key(addr));
|
|
|
|
match handshake.step(raw_bytes) {
|
|
HandshakeResult::Next(handshake, reply) => {
|
|
self.handshakes.insert(*addr, handshake);
|
|
if !reply.is_empty() {
|
|
self.inner_queue
|
|
.push_back(nakamoto::Io::Write(*addr, reply));
|
|
}
|
|
return;
|
|
}
|
|
HandshakeResult::Complete(transcoder, reply, link) => {
|
|
log::debug!("handshake with peer {} is complete", addr);
|
|
if !reply.is_empty() {
|
|
self.inner_queue
|
|
.push_back(nakamoto::Io::Write(*addr, reply));
|
|
}
|
|
let pipeline = Framer::new(transcoder);
|
|
self.inboxes.insert(
|
|
*addr,
|
|
Inbox {
|
|
pipeline,
|
|
deserializer: Deserializer::new(256),
|
|
},
|
|
);
|
|
self.inner.connected(*addr, link);
|
|
}
|
|
HandshakeResult::Error(err) => {
|
|
log::error!("invalid handshake input. Details: {}", err);
|
|
self.inner_queue.push_back(nakamoto::Io::Disconnect(
|
|
*addr,
|
|
service::DisconnectReason::Error(session::Error::Handshake(
|
|
err.to_string(),
|
|
)),
|
|
));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(Inbox {
|
|
pipeline,
|
|
deserializer,
|
|
}) = self.inboxes.get_mut(addr)
|
|
{
|
|
pipeline.input(raw_bytes);
|
|
for frame in pipeline {
|
|
let Ok(msg) = MuxMsg::try_from(frame) else {
|
|
// TODO: Disconnect peer.
|
|
log::error!("Message frame with invalid channel structure from {}", addr);
|
|
return;
|
|
};
|
|
match msg.channel {
|
|
0 => deserializer.input(&msg.data),
|
|
1 => { /* TODO: Send to git worker */ }
|
|
wrong_channel => {
|
|
// TODO: Disconnect peer.
|
|
log::error!("Wrong message channel {} from peer {}", wrong_channel, addr);
|
|
return;
|
|
}
|
|
};
|
|
}
|
|
|
|
for message in deserializer {
|
|
match message {
|
|
Ok(msg) => self.inner.received_message(addr, msg),
|
|
Err(err) => {
|
|
// TODO: Disconnect peer.
|
|
log::error!("Invalid message received from {}: {}", addr, err);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
log::debug!("Received message from unknown peer {}", addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R, S, W, G, H: Handshake> Iterator for Wire<R, S, W, G, H> {
|
|
type Item = nakamoto::Io<service::Event, service::DisconnectReason>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if let Some(event) = self.inner_queue.pop_front() {
|
|
return Some(event);
|
|
}
|
|
|
|
match self.inner.next() {
|
|
Some(Io::Write(addr, msgs)) => {
|
|
let mut buf = Vec::new();
|
|
for msg in msgs {
|
|
log::debug!("Write {:?} to {}", &msg, addr.ip());
|
|
|
|
msg.encode(&mut buf)
|
|
.expect("writing to an in-memory buffer doesn't fail");
|
|
}
|
|
let Inbox { pipeline, .. } = self.inboxes.get_mut(&addr).expect(
|
|
"broken handshake implementation: data sent before handshake was complete",
|
|
);
|
|
let data = pipeline.frame(buf).expect("oversized data for a frame");
|
|
let msg = MuxMsg { channel: 0, data };
|
|
Some(nakamoto::Io::Write(addr, msg.into()))
|
|
}
|
|
Some(Io::Event(e)) => Some(nakamoto::Io::Event(e)),
|
|
Some(Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
|
|
Some(Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
|
|
Some(Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
|
|
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use quickcheck_macros::quickcheck;
|
|
|
|
use crate::crypto::Unverified;
|
|
use crate::storage::refs::SignedRefs;
|
|
use crate::test::{arbitrary, assert_matches};
|
|
|
|
#[quickcheck]
|
|
fn prop_u8(input: u8) {
|
|
assert_eq!(deserialize::<u8>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_u16(input: u16) {
|
|
assert_eq!(deserialize::<u16>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_u32(input: u32) {
|
|
assert_eq!(deserialize::<u32>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_u64(input: u64) {
|
|
assert_eq!(deserialize::<u64>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_string(input: String) -> quickcheck::TestResult {
|
|
if input.len() > u8::MAX as usize {
|
|
return quickcheck::TestResult::discard();
|
|
}
|
|
assert_eq!(deserialize::<String>(&serialize(&input)).unwrap(), input);
|
|
|
|
quickcheck::TestResult::passed()
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_vec(input: Vec<String>) {
|
|
assert_eq!(
|
|
deserialize::<Vec<String>>(&serialize(&input.as_slice())).unwrap(),
|
|
input
|
|
);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_pubkey(input: PublicKey) {
|
|
assert_eq!(deserialize::<PublicKey>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_filter(input: filter::Filter) {
|
|
assert_eq!(
|
|
deserialize::<filter::Filter>(&serialize(&input)).unwrap(),
|
|
input
|
|
);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_id(input: Id) {
|
|
assert_eq!(deserialize::<Id>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_digest(input: Digest) {
|
|
assert_eq!(deserialize::<Digest>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_refs(input: Refs) {
|
|
assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_signature(input: arbitrary::ByteArray<64>) {
|
|
let signature = Signature::from(input.into_inner());
|
|
|
|
assert_eq!(
|
|
deserialize::<Signature>(&serialize(&signature)).unwrap(),
|
|
signature
|
|
);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_oid(input: arbitrary::ByteArray<20>) {
|
|
let oid = git::Oid::try_from(input.into_inner().as_slice()).unwrap();
|
|
|
|
assert_eq!(deserialize::<git::Oid>(&serialize(&oid)).unwrap(), oid);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_signed_refs(input: SignedRefs<Unverified>) {
|
|
assert_eq!(
|
|
deserialize::<SignedRefs<Unverified>>(&serialize(&input)).unwrap(),
|
|
input
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_string() {
|
|
assert_eq!(
|
|
serialize(&String::from("hello")),
|
|
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 = serialize(&f);
|
|
|
|
assert_matches!(
|
|
deserialize::<filter::Filter>(&bytes).unwrap_err(),
|
|
Error::InvalidFilterSize(_)
|
|
);
|
|
}
|
|
}
|