node: Move over to binary protocol
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
b3d3077ccc
commit
9d3030a093
|
|
@ -1,7 +1,8 @@
|
||||||
|
use std::io;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::protocol::message::Envelope;
|
use crate::protocol::message::Envelope;
|
||||||
use serde::Deserialize;
|
use crate::protocol::wire;
|
||||||
|
|
||||||
/// Message stream decoder.
|
/// Message stream decoder.
|
||||||
///
|
///
|
||||||
|
|
@ -21,7 +22,7 @@ impl<D> From<Vec<u8>> for Decoder<D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de, D: Deserialize<'de>> Decoder<D> {
|
impl<D: wire::Decode> Decoder<D> {
|
||||||
/// Create a new stream decoder.
|
/// Create a new stream decoder.
|
||||||
pub fn new(capacity: usize) -> Self {
|
pub fn new(capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -36,23 +37,36 @@ impl<'de, D: Deserialize<'de>> Decoder<D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode and return the next message. Returns [`None`] if nothing was decoded.
|
/// Decode and return the next message. Returns [`None`] if nothing was decoded.
|
||||||
pub fn decode_next(&mut self) -> Result<Option<D>, serde_json::Error> {
|
pub fn decode_next(&mut self) -> Result<Option<D>, wire::Error> {
|
||||||
let mut de = serde_json::Deserializer::from_reader(self.unparsed.as_slice()).into_iter();
|
let mut reader = io::Cursor::new(self.unparsed.as_mut_slice());
|
||||||
|
|
||||||
|
match D::decode(&mut reader) {
|
||||||
|
Ok(msg) => {
|
||||||
|
let pos = reader.position() as usize;
|
||||||
|
self.unparsed.drain(..pos);
|
||||||
|
|
||||||
match de.next() {
|
|
||||||
Some(Ok(msg)) => {
|
|
||||||
self.unparsed.drain(..de.byte_offset());
|
|
||||||
Ok(Some(msg))
|
Ok(Some(msg))
|
||||||
}
|
}
|
||||||
Some(Err(err)) if err.is_eof() => Ok(None),
|
Err(err) if err.is_eof() => Ok(None),
|
||||||
|
Err(err) => Err(err),
|
||||||
result => result.transpose(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de, D: Deserialize<'de>> Iterator for Decoder<D> {
|
impl<D: wire::Decode> io::Write for Decoder<D> {
|
||||||
type Item = Result<D, serde_json::Error>;
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.input(buf);
|
||||||
|
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: wire::Decode> Iterator for Decoder<D> {
|
||||||
|
type Item = Result<D, wire::Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.decode_next().transpose()
|
self.decode_next().transpose()
|
||||||
|
|
@ -64,14 +78,14 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use quickcheck_macros::quickcheck;
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
const MSG_HELLO: &[u8] = b"{\"cmd\":\"hello\"}";
|
const MSG_HELLO: &[u8] = &[5, b'h', b'e', b'l', b'l', b'o'];
|
||||||
const MSG_BYE: &[u8] = b"{\"cmd\":\"goodbye\"}";
|
const MSG_BYE: &[u8] = &[3, b'b', b'y', b'e'];
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn prop_decode_next(chunk_size: usize) {
|
fn prop_decode_next(chunk_size: usize) {
|
||||||
let mut bytes = vec![];
|
let mut bytes = vec![];
|
||||||
let mut msgs = vec![];
|
let mut msgs = vec![];
|
||||||
let mut decoder = Decoder::<serde_json::Value>::new(64);
|
let mut decoder = Decoder::<String>::new(8);
|
||||||
|
|
||||||
let chunk_size = 1 + chunk_size % MSG_HELLO.len() + MSG_BYE.len();
|
let chunk_size = 1 + chunk_size % MSG_HELLO.len() + MSG_BYE.len();
|
||||||
|
|
||||||
|
|
@ -88,17 +102,7 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(decoder.unparsed.len(), 0);
|
assert_eq!(decoder.unparsed.len(), 0);
|
||||||
assert_eq!(msgs.len(), 2);
|
assert_eq!(msgs.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(msgs[0], String::from("hello"));
|
||||||
msgs[0],
|
assert_eq!(msgs[1], String::from("bye"));
|
||||||
serde_json::json!({
|
|
||||||
"cmd": "hello",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
msgs[1],
|
|
||||||
serde_json::json!({
|
|
||||||
"cmd": "goodbye",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ pub mod peer;
|
||||||
pub mod wire;
|
pub mod wire;
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::{collections::VecDeque, fmt, io, net, net::IpAddr};
|
use std::{collections::VecDeque, fmt, net, net::IpAddr};
|
||||||
|
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
use fastrand::Rng;
|
use fastrand::Rng;
|
||||||
|
|
@ -26,6 +26,7 @@ use crate::identity::{Id, Project, PublicKey};
|
||||||
use crate::protocol::config::ProjectTracking;
|
use crate::protocol::config::ProjectTracking;
|
||||||
use crate::protocol::message::Message;
|
use crate::protocol::message::Message;
|
||||||
use crate::protocol::peer::{Peer, PeerError, PeerState};
|
use crate::protocol::peer::{Peer, PeerError, PeerState};
|
||||||
|
use crate::protocol::wire::Encode;
|
||||||
use crate::storage::{self, ReadRepository, WriteRepository};
|
use crate::storage::{self, ReadRepository, WriteRepository};
|
||||||
use crate::storage::{Inventory, WriteStorage};
|
use crate::storage::{Inventory, WriteStorage};
|
||||||
|
|
||||||
|
|
@ -546,7 +547,7 @@ where
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (peer, msgs) = if let Some(peer) = self.peers.get_mut(&peer) {
|
let (peer, msgs) = if let Some(peer) = self.peers.get_mut(&peer) {
|
||||||
let decoder = &mut peer.inbox();
|
let decoder = peer.inbox();
|
||||||
decoder.input(bytes);
|
decoder.input(bytes);
|
||||||
|
|
||||||
let mut msgs = Vec::with_capacity(1);
|
let mut msgs = Vec::with_capacity(1);
|
||||||
|
|
@ -555,8 +556,10 @@ where
|
||||||
Ok(Some(msg)) => msgs.push(msg),
|
Ok(Some(msg)) => msgs.push(msg),
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
|
|
||||||
Err(_err) => {
|
Err(err) => {
|
||||||
// TODO: Disconnect peer.
|
// TODO: Disconnect peer.
|
||||||
|
error!("Invalid message received from {}: {}", peer.addr, err);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -731,8 +734,13 @@ where
|
||||||
fn fetch(&mut self, proj_id: &Id, remote: &Url) {
|
fn fetch(&mut self, proj_id: &Id, remote: &Url) {
|
||||||
// TODO: Verify refs before adding them to storage.
|
// TODO: Verify refs before adding them to storage.
|
||||||
let mut repo = self.storage.repository(proj_id).unwrap();
|
let mut repo = self.storage.repository(proj_id).unwrap();
|
||||||
|
let mut path = remote.path.clone();
|
||||||
|
|
||||||
|
path.push(b'/');
|
||||||
|
path.extend(proj_id.to_string().into_bytes());
|
||||||
|
|
||||||
repo.fetch(&Url {
|
repo.fetch(&Url {
|
||||||
path: format!("/{}", proj_id).into(),
|
path,
|
||||||
..remote.clone()
|
..remote.clone()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -757,22 +765,24 @@ impl<S, T, G> Context<S, T, G> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) {
|
fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) {
|
||||||
let mut buf = io::Cursor::new(Vec::new());
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
for msg in msgs {
|
for msg in msgs {
|
||||||
debug!("Write {:?} to {}", &msg, remote.ip());
|
debug!("Write {:?} to {}", &msg, remote.ip());
|
||||||
|
|
||||||
let envelope = self.config.network.envelope(msg);
|
let envelope = self.config.network.envelope(msg);
|
||||||
serde_json::to_writer(&mut buf, &envelope).unwrap();
|
envelope
|
||||||
|
.encode(&mut buf)
|
||||||
|
.expect("writing to an in-memory buffer doesn't fail");
|
||||||
}
|
}
|
||||||
self.io.push_back(Io::Write(remote, buf.into_inner()));
|
self.io.push_back(Io::Write(remote, buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, remote: net::SocketAddr, msg: Message) {
|
fn write(&mut self, remote: net::SocketAddr, msg: Message) {
|
||||||
debug!("Write {:?} to {}", &msg, remote.ip());
|
debug!("Write {:?} to {}", &msg, remote.ip());
|
||||||
|
|
||||||
let envelope = self.config.network.envelope(msg);
|
let envelope = self.config.network.envelope(msg);
|
||||||
let bytes = serde_json::to_vec(&envelope).unwrap();
|
let bytes = wire::serialize(&envelope);
|
||||||
|
|
||||||
self.io.push_back(Io::Write(remote, bytes));
|
self.io.push_back(Io::Write(remote, bytes));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::net;
|
||||||
use git_url::Url;
|
use git_url::Url;
|
||||||
|
|
||||||
use crate::collections::HashSet;
|
use crate::collections::HashSet;
|
||||||
|
use crate::git;
|
||||||
use crate::identity::{Id, PublicKey};
|
use crate::identity::{Id, PublicKey};
|
||||||
use crate::protocol::message::{Address, Envelope, Message};
|
use crate::protocol::message::{Address, Envelope, Message};
|
||||||
|
|
||||||
|
|
@ -88,7 +89,11 @@ impl Default for Config {
|
||||||
remote_tracking: RemoteTracking::default(),
|
remote_tracking: RemoteTracking::default(),
|
||||||
relay: true,
|
relay: true,
|
||||||
listen: vec![],
|
listen: vec![],
|
||||||
git_url: Url::default(),
|
git_url: Url {
|
||||||
|
scheme: git::url::Scheme::File,
|
||||||
|
path: "/dev/null".to_owned().into(),
|
||||||
|
..Url::default()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::storage;
|
||||||
use crate::storage::refs::SignedRefs;
|
use crate::storage::refs::SignedRefs;
|
||||||
|
|
||||||
/// Message envelope. All messages sent over the network are wrapped in this type.
|
/// Message envelope. All messages sent over the network are wrapped in this type.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
/// Network magic constant. Used to differentiate networks.
|
/// Network magic constant. Used to differentiate networks.
|
||||||
pub magic: u32,
|
pub magic: u32,
|
||||||
|
|
@ -47,6 +47,26 @@ pub enum Address {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl wire::Encode for Envelope {
|
||||||
|
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
||||||
|
let mut n = 0;
|
||||||
|
|
||||||
|
n += self.magic.encode(writer)?;
|
||||||
|
n += self.msg.encode(writer)?;
|
||||||
|
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl wire::Decode for Envelope {
|
||||||
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
||||||
|
let magic = u32::decode(reader)?;
|
||||||
|
let msg = Message::decode(reader)?;
|
||||||
|
|
||||||
|
Ok(Self { magic, msg })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl wire::Encode for Address {
|
impl wire::Encode for Address {
|
||||||
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
|
|
@ -84,6 +104,13 @@ impl wire::Decode for Address {
|
||||||
|
|
||||||
Ok(Self::Ip { ip, port })
|
Ok(Self::Ip { ip, port })
|
||||||
}
|
}
|
||||||
|
2 => {
|
||||||
|
let octets: [u8; 16] = wire::Decode::decode(reader)?;
|
||||||
|
let ip = net::IpAddr::from(net::Ipv6Addr::from(octets));
|
||||||
|
let port = u16::decode(reader)?;
|
||||||
|
|
||||||
|
Ok(Self::Ip { ip, port })
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
@ -108,6 +135,7 @@ pub struct NodeAnnouncement {
|
||||||
impl NodeAnnouncement {
|
impl NodeAnnouncement {
|
||||||
/// Verify a signature on this message.
|
/// Verify a signature on this message.
|
||||||
pub fn verify(&self, signature: &crypto::Signature) -> bool {
|
pub fn verify(&self, signature: &crypto::Signature) -> bool {
|
||||||
|
// TODO: Use binary serialization.
|
||||||
let msg = serde_json::to_vec(self).unwrap();
|
let msg = serde_json::to_vec(self).unwrap();
|
||||||
self.id.verify(signature, &msg).is_ok()
|
self.id.verify(signature, &msg).is_ok()
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +143,7 @@ impl NodeAnnouncement {
|
||||||
|
|
||||||
/// Message payload.
|
/// Message payload.
|
||||||
/// These are the messages peers send to each other.
|
/// These are the messages peers send to each other.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
/// Say hello to a peer. This is the first message sent to a peer after connection.
|
/// Say hello to a peer. This is the first message sent to a peer after connection.
|
||||||
Hello {
|
Hello {
|
||||||
|
|
@ -295,9 +323,60 @@ impl wire::Decode for Message {
|
||||||
|
|
||||||
Ok(Self::RefsUpdate { id, signer, refs })
|
Ok(Self::RefsUpdate { id, signer, refs })
|
||||||
}
|
}
|
||||||
_ => {
|
n => {
|
||||||
todo!();
|
todo!("Mesage type {} is not yet implemented", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
use crate::decoder::Decoder;
|
||||||
|
use crate::protocol::wire::{self, Encode};
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn prop_message_encode_decode(message: Message) {
|
||||||
|
assert_eq!(
|
||||||
|
wire::deserialize::<Message>(&wire::serialize(&message)).unwrap(),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn prop_envelope_encode_decode(envelope: Envelope) {
|
||||||
|
assert_eq!(
|
||||||
|
wire::deserialize::<Envelope>(&wire::serialize(&envelope)).unwrap(),
|
||||||
|
envelope
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prop_envelope_decoder() {
|
||||||
|
fn property(items: Vec<Envelope>) {
|
||||||
|
let mut decoder = Decoder::<Envelope>::new(8);
|
||||||
|
|
||||||
|
for item in &items {
|
||||||
|
item.encode(&mut decoder).unwrap();
|
||||||
|
}
|
||||||
|
for item in items {
|
||||||
|
assert_eq!(decoder.next().unwrap().unwrap(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickcheck::QuickCheck::new()
|
||||||
|
.gen(quickcheck::Gen::new(16))
|
||||||
|
.quickcheck(property as fn(items: Vec<Envelope>));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn prop_addr(addr: Address) {
|
||||||
|
assert_eq!(
|
||||||
|
wire::deserialize::<Address>(&wire::serialize(&addr)).unwrap(),
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,17 @@ pub enum Error {
|
||||||
InvalidSize { expected: usize, actual: usize },
|
InvalidSize { expected: usize, actual: usize },
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidRefName(#[from] fmt::Error),
|
InvalidRefName(#[from] fmt::Error),
|
||||||
|
#[error("invalid git url `{url}`: {error}")]
|
||||||
|
InvalidGitUrl {
|
||||||
|
url: String,
|
||||||
|
error: git::url::parse::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn is_eof(&self) -> bool {
|
||||||
|
matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Encode {
|
pub trait Encode {
|
||||||
|
|
@ -45,6 +56,13 @@ pub fn serialize<T: Encode + ?Sized>(data: &T) -> Vec<u8> {
|
||||||
buffer
|
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 {
|
impl Encode for u8 {
|
||||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
writer.write_u8(*self)?;
|
writer.write_u8(*self)?;
|
||||||
|
|
@ -79,13 +97,14 @@ impl Encode for u64 {
|
||||||
|
|
||||||
impl Encode for usize {
|
impl Encode for usize {
|
||||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
let u = (*self)
|
assert!(
|
||||||
.try_into()
|
*self <= u32::MAX as usize,
|
||||||
.map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
|
"Cannot encode sizes larger than {}",
|
||||||
|
u32::MAX
|
||||||
|
);
|
||||||
|
writer.write_u32::<NetworkEndian>(*self as u32)?;
|
||||||
|
|
||||||
writer.write_u32::<NetworkEndian>(u)?;
|
Ok(mem::size_of::<u32>())
|
||||||
|
|
||||||
Ok(mem::size_of_val(&u))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,14 +153,18 @@ impl Encode for net::IpAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for str {
|
impl Encode for &str {
|
||||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
let mut n = 0;
|
assert!(self.len() <= u8::MAX as usize);
|
||||||
|
|
||||||
n += self.len().encode(writer)?;
|
let n = (self.len() as u8).encode(writer)?;
|
||||||
n += self.as_bytes().encode(writer)?;
|
let bytes = self.as_bytes();
|
||||||
|
|
||||||
Ok(n)
|
// 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +299,7 @@ impl Decode for u64 {
|
||||||
|
|
||||||
impl Decode for usize {
|
impl Decode for usize {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let size: usize = u64::decode(reader)?
|
let size: usize = u32::decode(reader)?
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
|
.map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
|
||||||
|
|
||||||
|
|
@ -311,8 +334,8 @@ where
|
||||||
|
|
||||||
impl Decode for String {
|
impl Decode for String {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let len = usize::decode(reader)?;
|
let len = u8::decode(reader)?;
|
||||||
let mut bytes = vec![0; len];
|
let mut bytes = vec![0; len as usize];
|
||||||
|
|
||||||
reader.read_exact(&mut bytes)?;
|
reader.read_exact(&mut bytes)?;
|
||||||
|
|
||||||
|
|
@ -324,10 +347,11 @@ impl Decode for String {
|
||||||
|
|
||||||
impl Decode for git::Url {
|
impl Decode for git::Url {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let string = String::decode(reader)?;
|
let url = String::decode(reader)?;
|
||||||
|
let url = Self::from_bytes(url.as_bytes())
|
||||||
|
.map_err(|error| Error::InvalidGitUrl { url, error })?;
|
||||||
|
|
||||||
Self::from_bytes(string.as_bytes())
|
Ok(url)
|
||||||
.map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,3 +370,118 @@ impl Decode for Digest {
|
||||||
Ok(Self::from(bytes))
|
Ok(Self::from(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
use crate::crypto::Unverified;
|
||||||
|
use crate::storage::refs::SignedRefs;
|
||||||
|
use crate::test::arbitrary;
|
||||||
|
|
||||||
|
#[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_usize(input: usize) -> quickcheck::TestResult {
|
||||||
|
if input > u32::MAX as usize {
|
||||||
|
return quickcheck::TestResult::discard();
|
||||||
|
}
|
||||||
|
assert_eq!(deserialize::<usize>(&serialize(&input)).unwrap(), input);
|
||||||
|
|
||||||
|
quickcheck::TestResult::passed()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_pubkey(input: PublicKey) {
|
||||||
|
assert_eq!(deserialize::<PublicKey>(&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_git_url() {
|
||||||
|
let url = git::Url {
|
||||||
|
scheme: git::url::Scheme::Https,
|
||||||
|
path: "/git".to_owned().into(),
|
||||||
|
host: Some("seed.radicle.xyz".to_owned()),
|
||||||
|
port: Some(8888),
|
||||||
|
..git::Url::default()
|
||||||
|
};
|
||||||
|
assert_eq!(deserialize::<git::Url>(&serialize(&url)).unwrap(), url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ impl ReadStorage for Storage {
|
||||||
fn url(&self) -> git::Url {
|
fn url(&self) -> git::Url {
|
||||||
git::Url {
|
git::Url {
|
||||||
scheme: git_url::Scheme::File,
|
scheme: git_url::Scheme::File,
|
||||||
host: Some(self.path.to_string_lossy().to_string()),
|
host: None,
|
||||||
|
path: self.path.to_string_lossy().to_string().into(),
|
||||||
..git::Url::default()
|
..git::Url::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
use std::net;
|
||||||
use std::ops::RangeBounds;
|
use std::ops::RangeBounds;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
@ -7,13 +8,15 @@ use nonempty::NonEmpty;
|
||||||
use quickcheck::Arbitrary;
|
use quickcheck::Arbitrary;
|
||||||
|
|
||||||
use crate::collections::HashMap;
|
use crate::collections::HashMap;
|
||||||
use crate::crypto::{self, Signer};
|
use crate::crypto::{self, Signer, Unverified};
|
||||||
use crate::crypto::{PublicKey, SecretKey};
|
use crate::crypto::{PublicKey, SecretKey};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::hash;
|
use crate::hash;
|
||||||
use crate::identity::{Delegate, Did, Doc, Id, Project};
|
use crate::identity::{Delegate, Did, Doc, Id, Project};
|
||||||
|
use crate::protocol::message::{Address, Envelope, Message};
|
||||||
|
use crate::protocol::{NodeId, Timestamp};
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::{Refs, SignedRefs};
|
||||||
use crate::test::storage::MockStorage;
|
use crate::test::storage::MockStorage;
|
||||||
|
|
||||||
use super::crypto::MockSigner;
|
use super::crypto::MockSigner;
|
||||||
|
|
@ -35,6 +38,75 @@ pub fn gen<T: Arbitrary>(size: usize) -> T {
|
||||||
T::arbitrary(&mut gen)
|
T::arbitrary(&mut gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ByteArray<const N: usize>([u8; N]);
|
||||||
|
|
||||||
|
impl<const N: usize> ByteArray<N> {
|
||||||
|
pub fn into_inner(self) -> [u8; N] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Arbitrary for ByteArray<N> {
|
||||||
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
|
let mut bytes: [u8; N] = [0; N];
|
||||||
|
for byte in &mut bytes {
|
||||||
|
*byte = u8::arbitrary(g);
|
||||||
|
}
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Envelope {
|
||||||
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
|
Self {
|
||||||
|
magic: u32::arbitrary(g),
|
||||||
|
msg: Message::arbitrary(g),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Message {
|
||||||
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
|
let type_id = g.choose(&[4, 6, 8]).unwrap();
|
||||||
|
|
||||||
|
match type_id {
|
||||||
|
4 => Self::GetInventory {
|
||||||
|
ids: Vec::<Id>::arbitrary(g),
|
||||||
|
},
|
||||||
|
6 => Self::Inventory {
|
||||||
|
node: NodeId::arbitrary(g),
|
||||||
|
inv: Vec::<Id>::arbitrary(g),
|
||||||
|
timestamp: Timestamp::arbitrary(g),
|
||||||
|
},
|
||||||
|
8 => Self::RefsUpdate {
|
||||||
|
id: Id::arbitrary(g),
|
||||||
|
signer: PublicKey::arbitrary(g),
|
||||||
|
refs: SignedRefs::<Unverified>::arbitrary(g),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Address {
|
||||||
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
|
if bool::arbitrary(g) {
|
||||||
|
Address::Ip {
|
||||||
|
ip: net::IpAddr::V4(net::Ipv4Addr::from(u32::arbitrary(g))),
|
||||||
|
port: u16::arbitrary(g),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let octets: [u8; 16] = ByteArray::<16>::arbitrary(g).into_inner();
|
||||||
|
|
||||||
|
Address::Ip {
|
||||||
|
ip: net::IpAddr::V6(net::Ipv6Addr::from(octets)),
|
||||||
|
port: u16::arbitrary(g),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Arbitrary for storage::Remotes<crypto::Verified> {
|
impl Arbitrary for storage::Remotes<crypto::Verified> {
|
||||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
let remotes: HashMap<storage::RemoteId, storage::Remote<crypto::Verified>> =
|
let remotes: HashMap<storage::RemoteId, storage::Remote<crypto::Verified>> =
|
||||||
|
|
@ -104,6 +176,16 @@ impl Arbitrary for Doc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for SignedRefs<Unverified> {
|
||||||
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
|
let bytes: ByteArray<64> = Arbitrary::arbitrary(g);
|
||||||
|
let signature = crypto::Signature::from(bytes.into_inner());
|
||||||
|
let refs = Refs::arbitrary(g);
|
||||||
|
|
||||||
|
Self::new(refs, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Arbitrary for Refs {
|
impl Arbitrary for Refs {
|
||||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
let mut refs: BTreeMap<git::RefString, storage::Oid> = BTreeMap::new();
|
let mut refs: BTreeMap<git::RefString, storage::Oid> = BTreeMap::new();
|
||||||
|
|
@ -146,12 +228,8 @@ impl Arbitrary for storage::Remote<crypto::Verified> {
|
||||||
|
|
||||||
impl Arbitrary for MockSigner {
|
impl Arbitrary for MockSigner {
|
||||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
let mut bytes: [u8; 32] = [0; 32];
|
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
|
||||||
|
MockSigner::from(SecretKey::from(bytes.into_inner()))
|
||||||
for byte in &mut bytes {
|
|
||||||
*byte = u8::arbitrary(g);
|
|
||||||
}
|
|
||||||
MockSigner::from(SecretKey::from(bytes))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,12 +251,8 @@ impl Arbitrary for PublicKey {
|
||||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
use ed25519_consensus::SigningKey;
|
use ed25519_consensus::SigningKey;
|
||||||
|
|
||||||
let mut bytes: [u8; 32] = [0; 32];
|
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
|
||||||
|
let sk = SigningKey::from(bytes.into_inner());
|
||||||
for byte in &mut bytes {
|
|
||||||
*byte = u8::arbitrary(g);
|
|
||||||
}
|
|
||||||
let sk = SigningKey::from(bytes);
|
|
||||||
let vk = sk.verification_key();
|
let vk = sk.verification_key();
|
||||||
|
|
||||||
PublicKey(vk)
|
PublicKey(vk)
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) {
|
pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) {
|
||||||
let bytes = serde_json::to_vec(&self.config().network.envelope(msg)).unwrap();
|
let bytes = wire::serialize(&self.config().network.envelope(msg));
|
||||||
|
|
||||||
self.protocol.received_bytes(peer, &bytes);
|
self.protocol.received_bytes(peer, &bytes);
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ where
|
||||||
pub fn connect_from(&mut self, peer: &Self) {
|
pub fn connect_from(&mut self, peer: &Self) {
|
||||||
let remote = simulator::Peer::<Protocol<S>>::addr(peer);
|
let remote = simulator::Peer::<Protocol<S>>::addr(peer);
|
||||||
let local = net::SocketAddr::new(self.ip, self.rng.u16(..));
|
let local = net::SocketAddr::new(self.ip, self.rng.u16(..));
|
||||||
let git = format!("file://{}.git", remote.ip());
|
let git = format!("file:///{}.git", remote.ip());
|
||||||
let git = Url::from_bytes(git.as_bytes()).unwrap();
|
let git = Url::from_bytes(git.as_bytes()).unwrap();
|
||||||
|
|
||||||
self.initialize();
|
self.initialize();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue