node: Implement binary encoding for messages

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-09-08 12:09:44 +02:00
parent c7f78eafb6
commit b3d3077ccc
No known key found for this signature in database
11 changed files with 638 additions and 88 deletions

View File

@ -9,6 +9,7 @@ edition = "2021"
anyhow = { version = "1" }
bs58 = { version = "0.4.0" }
ed25519-consensus = { version = "2.0.1" }
byteorder = { version = "1" }
chrono = { version = "0.4.0" }
colored = { version = "1.9.0" }
crossbeam-channel = { version = "0.5.6" }

View File

@ -74,13 +74,13 @@ impl std::hash::Hash for PublicKey {
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.encode())
write!(f, "{}", self.to_human())
}
}
impl From<PublicKey> for String {
fn from(other: PublicKey) -> Self {
other.encode()
other.to_human()
}
}
@ -96,8 +96,16 @@ impl From<ed25519::VerificationKey> for PublicKey {
}
}
impl TryFrom<[u8; 32]> for PublicKey {
type Error = ed25519::Error;
fn try_from(other: [u8; 32]) -> Result<Self, Self::Error> {
Ok(Self(ed25519::VerificationKey::try_from(other)?))
}
}
impl PublicKey {
pub fn encode(&self) -> String {
pub fn to_human(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0)
}
}
@ -131,3 +139,18 @@ impl Deref for PublicKey {
&self.0
}
}
#[cfg(test)]
mod test {
use crate::crypto::PublicKey;
use quickcheck_macros::quickcheck;
use std::str::FromStr;
#[quickcheck]
fn prop_encode_decode(input: PublicKey) {
let encoded = input.to_string();
let decoded = PublicKey::from_str(&encoded).unwrap();
assert_eq!(input, decoded);
}
}

View File

@ -9,6 +9,7 @@ use crate::storage::refs::Refs;
use crate::storage::RemoteId;
pub use ext::Oid;
pub use git_ref_format as fmt;
pub use git_ref_format::{refname, RefStr, RefString};
pub use git_url as url;
pub use git_url::Url;

View File

@ -1,5 +1,4 @@
use std::fmt;
use std::str::FromStr;
use std::{convert::TryInto, fmt};
use serde::{Deserialize, Serialize};
use sha2::{
@ -10,8 +9,6 @@ use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum DecodeError {
#[error(transparent)]
Multibase(#[from] multibase::Error),
#[error("invalid digest length {0}")]
InvalidLength(usize),
}
@ -24,38 +21,17 @@ impl Digest {
pub fn new(bytes: impl AsRef<[u8]>) -> Self {
Self::from(Sha256::digest(bytes))
}
pub fn encode(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0)
}
pub fn decode(s: &str) -> Result<Self, DecodeError> {
let (_, bytes) = multibase::decode(s)?;
let array = bytes
.try_into()
.map_err(|v: Vec<u8>| DecodeError::InvalidLength(v.len()))?;
Ok(Self(array))
}
}
impl FromStr for Digest {
type Err = DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::decode(s)
}
}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
impl AsRef<[u8; 32]> for Digest {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl fmt::Debug for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash({})", self.encode())
write!(f, "Hash({})", self)
}
}
@ -74,22 +50,20 @@ impl From<[u8; 32]> for Digest {
}
}
impl TryFrom<&[u8]> for Digest {
type Error = DecodeError;
fn try_from(bytes: &[u8]) -> Result<Self, DecodeError> {
let bytes: [u8; 32] = bytes
.try_into()
.map_err(|_| DecodeError::InvalidLength(bytes.len()))?;
Ok(bytes.into())
}
}
impl From<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>> for Digest {
fn from(array: GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>) -> Self {
Self(array.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn prop_encode_decode(input: Digest) {
let encoded = input.encode();
let decoded = Digest::decode(&encoded).unwrap();
assert_eq!(input, decoded);
}
}

View File

@ -1,3 +1,4 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::{ffi::OsString, fmt, io, str::FromStr};
@ -20,6 +21,8 @@ pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new("radicle.json"));
pub enum IdError {
#[error("invalid digest: {0}")]
InvalidDigest(#[from] hash::DecodeError),
#[error(transparent)]
Multibase(#[from] multibase::Error),
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -27,27 +30,34 @@ pub struct Id(hash::Digest);
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.encode())
f.write_str(&self.to_human())
}
}
impl fmt::Debug for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ProjId({})", self.encode())
write!(f, "Id({})", self)
}
}
impl Id {
pub fn encode(&self) -> String {
pub fn to_human(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0.as_ref())
}
pub fn from_human(s: &str) -> Result<Self, IdError> {
let (_, bytes) = multibase::decode(s)?;
let array: hash::Digest = bytes.as_slice().try_into()?;
Ok(Self(array))
}
}
impl FromStr for Id {
type Err = IdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(hash::Digest::from_str(s)?))
Self::from_human(s)
}
}
@ -66,6 +76,14 @@ impl From<hash::Digest> for Id {
}
}
impl Deref for Id {
type Target = hash::Digest;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl serde::Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -98,7 +116,7 @@ pub struct Did(crypto::PublicKey);
impl Did {
pub fn encode(&self) -> String {
format!("did:key:{}", self.0.encode())
format!("did:key:{}", self.0.to_human())
}
pub fn decode(input: &str) -> Result<Self, DidError> {
@ -219,9 +237,9 @@ mod test {
}
#[quickcheck]
fn prop_encode_decode(input: PublicKey) {
fn prop_from_str(input: Id) {
let encoded = input.to_string();
let decoded = PublicKey::from_str(&encoded).unwrap();
let decoded = Id::from_str(&encoded).unwrap();
assert_eq!(input, decoded);
}

View File

@ -2,6 +2,7 @@
pub mod config;
pub mod message;
pub mod peer;
pub mod wire;
use std::ops::{Deref, DerefMut};
use std::{collections::VecDeque, fmt, io, net, net::IpAddr};
@ -667,6 +668,16 @@ pub struct Context<S, T, G> {
rng: Rng,
}
impl<S, T, G> Context<S, T, G>
where
T: storage::ReadStorage,
G: crypto::Signer,
{
pub(crate) fn id(&self) -> NodeId {
*self.signer.public_key()
}
}
impl<'r, S, T, G> Context<S, T, G>
where
T: storage::WriteStorage<'r>,
@ -692,10 +703,6 @@ where
}
}
pub(crate) fn id(&self) -> NodeId {
*self.signer.public_key()
}
/// Process a peer inventory announcement by updating our routing table.
fn process_inventory(&mut self, inventory: &Inventory, from: NodeId, remote: &Url) {
for proj_id in inventory {

View File

@ -1,10 +1,12 @@
use std::net;
use git_url::Url;
use byteorder::NetworkEndian;
use serde::{Deserialize, Serialize};
use crate::crypto;
use crate::git;
use crate::identity::Id;
use crate::protocol::wire;
use crate::protocol::{Context, NodeId, Timestamp, PROTOCOL_VERSION};
use crate::storage;
use crate::storage::refs::SignedRefs;
@ -28,13 +30,6 @@ pub struct Hostname(String);
/// Peer public protocol address.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Address {
/// Tor V3 onion address.
Onion {
key: crypto::PublicKey,
port: u16,
checksum: u16,
version: u8,
},
Ip {
ip: net::IpAddr,
port: u16,
@ -43,6 +38,57 @@ pub enum Address {
host: Hostname,
port: u16,
},
/// Tor V3 onion address.
Onion {
key: crypto::PublicKey,
port: u16,
checksum: u16,
version: u8,
},
}
impl wire::Encode for Address {
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
let mut n = 0;
match self {
Self::Ip { ip, port } => {
match ip {
net::IpAddr::V4(addr) => {
n += 1u8.encode(writer)?;
n += addr.octets().encode(writer)?;
}
net::IpAddr::V6(addr) => {
n += 2u8.encode(writer)?;
n += addr.octets().encode(writer)?;
}
}
n += port.encode(writer)?;
}
Self::Hostname { .. } => todo!(),
Self::Onion { .. } => todo!(),
}
Ok(n)
}
}
impl wire::Decode for Address {
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
use byteorder::ReadBytesExt;
match reader.read_u8()? {
1 => {
let octets: [u8; 4] = wire::Decode::decode(reader)?;
let ip = net::IpAddr::from(net::Ipv4Addr::from(octets));
let port = u16::decode(reader)?;
Ok(Self::Ip { ip, port })
}
_ => {
todo!();
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -78,7 +124,7 @@ pub enum Message {
timestamp: Timestamp,
version: u32,
addrs: Vec<Address>,
git: Url,
git: git::Url,
},
Node {
/// Signature over the announcement, by the node being announced.
@ -91,11 +137,9 @@ pub enum Message {
/// Send our inventory to a peer. Sent in response to [`Message::GetInventory`].
/// Nb. This should be the whole inventory, not a partial update.
Inventory {
node: NodeId,
inv: Vec<Id>,
timestamp: Timestamp,
/// Original peer this inventory came from. We don't set this when we
/// are the originator, only when relaying.
origin: Option<NodeId>,
},
/// Project refs were updated. Includes the signature of the user who updated
/// their view of the project.
@ -110,7 +154,7 @@ pub enum Message {
}
impl Message {
pub fn hello(id: NodeId, timestamp: Timestamp, addrs: Vec<Address>, git: Url) -> Self {
pub fn hello(id: NodeId, timestamp: Timestamp, addrs: Vec<Address>, git: git::Url) -> Self {
Self::Hello {
id,
timestamp,
@ -133,18 +177,127 @@ impl Message {
pub fn inventory<S, T, G>(ctx: &Context<S, T, G>) -> Result<Self, storage::Error>
where
T: storage::ReadStorage,
G: crypto::Signer,
{
let timestamp = ctx.timestamp();
let inv = ctx.storage.inventory()?;
Ok(Self::Inventory {
timestamp,
node: ctx.id(),
inv,
origin: None,
timestamp,
})
}
pub fn get_inventory(ids: impl Into<Vec<Id>>) -> Self {
Self::GetInventory { ids: ids.into() }
}
pub fn type_id(&self) -> u16 {
match self {
Self::Hello { .. } => 0,
Self::Node { .. } => 2,
Self::GetInventory { .. } => 4,
Self::Inventory { .. } => 6,
Self::RefsUpdate { .. } => 8,
}
}
}
impl wire::Encode for Message {
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
let mut n = self.type_id().encode(writer)?;
match self {
Self::Hello {
id,
timestamp,
version,
addrs,
git,
} => {
n += id.encode(writer)?;
n += timestamp.encode(writer)?;
n += version.encode(writer)?;
n += addrs.as_slice().encode(writer)?;
n += git.encode(writer)?;
}
Self::RefsUpdate { id, signer, refs } => {
n += id.encode(writer)?;
n += signer.encode(writer)?;
n += refs.encode(writer)?;
}
Self::GetInventory { ids } => {
n += ids.as_slice().encode(writer)?;
}
Self::Inventory {
node,
inv,
timestamp,
} => {
n += node.encode(writer)?;
n += inv.as_slice().encode(writer)?;
n += timestamp.encode(writer)?;
}
Self::Node { .. } => {
todo!();
}
}
Ok(n)
}
}
impl wire::Decode for Message {
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
use byteorder::ReadBytesExt;
let type_id = reader.read_u16::<NetworkEndian>()?;
match type_id {
0 => {
let id = NodeId::decode(reader)?;
let timestamp = Timestamp::decode(reader)?;
let version = u32::decode(reader)?;
let addrs = Vec::<Address>::decode(reader)?;
let git = git::Url::decode(reader)?;
Ok(Self::Hello {
id,
timestamp,
version,
addrs,
git,
})
}
2 => {
todo!();
}
4 => {
let ids = Vec::<Id>::decode(reader)?;
Ok(Self::GetInventory { ids })
}
6 => {
let node = NodeId::decode(reader)?;
let inv = Vec::<Id>::decode(reader)?;
let timestamp = Timestamp::decode(reader)?;
Ok(Self::Inventory {
node,
inv,
timestamp,
})
}
8 => {
let id = Id::decode(reader)?;
let signer = crypto::PublicKey::decode(reader)?;
let refs = SignedRefs::decode(reader)?;
Ok(Self::RefsUpdate { id, signer, refs })
}
_ => {
todo!();
}
}
}
}

View File

@ -163,11 +163,11 @@ impl Peer {
ctx.write(self.addr, inventory);
}
(
PeerState::Negotiated { id, git, .. },
PeerState::Negotiated { git, .. },
Message::Inventory {
node,
timestamp,
inv,
origin,
},
) => {
let now = ctx.clock.local_time();
@ -184,13 +184,13 @@ impl Peer {
} else {
return Ok(None);
}
ctx.process_inventory(&inv, origin.unwrap_or(*id), git);
ctx.process_inventory(&inv, node, git);
if ctx.config.relay {
return Ok(Some(Message::Inventory {
timestamp,
node,
inv,
origin: origin.or(Some(*id)),
timestamp,
}));
}
}

348
node/src/protocol/wire.rs Normal file
View File

@ -0,0 +1,348 @@
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ops::Deref;
use std::string::FromUtf8Error;
use std::{io, mem, net};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use crate::crypto::{PublicKey, Signature};
use crate::git;
use crate::git::fmt;
use crate::hash::Digest;
use crate::identity::Id;
use crate::storage::refs::Refs;
#[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(transparent)]
InvalidRefName(#[from] fmt::Error),
}
pub trait Encode {
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error>;
}
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
}
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 usize {
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
let u = (*self)
.try_into()
.map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
writer.write_u32::<NetworkEndian>(u)?;
Ok(mem::size_of_val(&u))
}
}
impl Encode for PublicKey {
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
self.as_bytes().encode(writer)
}
}
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<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().encode(writer)?;
for item in self.iter() {
n += item.encode(writer)?;
}
Ok(n)
}
}
impl Encode for net::IpAddr {
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
match self {
net::IpAddr::V4(addr) => addr.octets().encode(writer),
net::IpAddr::V6(addr) => addr.octets().encode(writer),
}
}
}
impl Encode for str {
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
let mut n = 0;
n += self.len().encode(writer)?;
n += self.as_bytes().encode(writer)?;
Ok(n)
}
}
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 mut n = self.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.to_bytes().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 = usize::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 = usize::decode(reader)?;
#[allow(non_upper_case_globals)]
const expected: usize = mem::size_of::<git2::Oid>();
if len != expected {
return Err(Error::InvalidSize {
expected,
actual: len,
});
}
let buf: [u8; expected] = Decode::decode(reader)?;
let oid = git2::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 Decode for usize {
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
let size: usize = u64::decode(reader)?
.try_into()
.map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
Ok(size)
}
}
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: usize = usize::decode(reader)?;
let mut vec = Vec::with_capacity(len);
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 = usize::decode(reader)?;
let mut bytes = vec![0; len];
reader.read_exact(&mut bytes)?;
let string = String::from_utf8(bytes)?;
Ok(string)
}
}
impl Decode for git::Url {
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
let string = String::decode(reader)?;
Self::from_bytes(string.as_bytes())
.map_err(|e| Error::Io(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())))
}
}
impl Decode for Id {
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
let digest: Digest = Decode::decode(reader)?;
Ok(Self::from(digest))
}
}
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))
}
}

View File

@ -16,6 +16,7 @@ use crate::crypto;
use crate::crypto::{PublicKey, Signature, Signer, Unverified, Verified};
use crate::git;
use crate::git::Oid;
use crate::protocol::wire;
use crate::storage;
use crate::storage::{ReadRepository, RemoteId, WriteRepository};
@ -295,6 +296,30 @@ impl SignedRefs<Verified> {
}
}
impl<V> wire::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 wire::Decode for SignedRefs<Unverified> {
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
let refs = Refs::decode(reader)?;
let signature = Signature::decode(reader)?;
Ok(Self {
refs,
signature,
_verified: PhantomData,
})
}
}
impl<V> Deref for SignedRefs<V> {
type Target = Refs;

View File

@ -146,9 +146,9 @@ fn test_inventory_sync() {
alice.receive(
&bob.addr(),
Message::Inventory {
node: bob.id(),
timestamp: now,
inv: projs.clone(),
origin: None,
},
);
@ -213,9 +213,9 @@ fn test_inventory_relay_bad_timestamp() {
alice.receive(
&bob.addr(),
Message::Inventory {
node: bob.id(),
timestamp,
inv: vec![],
origin: None,
},
);
assert_matches!(
@ -240,15 +240,15 @@ fn test_inventory_relay() {
alice.receive(
&bob.addr(),
Message::Inventory {
node: bob.id(),
timestamp: now,
inv: inv.clone(),
origin: None,
},
);
assert_matches!(
alice.messages(&eve.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(bob.id()) && timestamp == now
Some(Message::Inventory { node, timestamp, .. })
if node == bob.id() && timestamp == now
);
assert_matches!(
alice.messages(&bob.addr()).next(),
@ -259,9 +259,9 @@ fn test_inventory_relay() {
alice.receive(
&bob.addr(),
Message::Inventory {
node: bob.id(),
timestamp: now,
inv: inv.clone(),
origin: None,
},
);
assert_matches!(
@ -273,15 +273,15 @@ fn test_inventory_relay() {
alice.receive(
&bob.addr(),
Message::Inventory {
node: bob.id(),
timestamp: now + 1,
inv: inv.clone(),
origin: None,
},
);
assert_matches!(
alice.messages(&eve.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(bob.id()) && timestamp == now + 1,
Some(Message::Inventory { node, timestamp, .. })
if node == bob.id() && timestamp == now + 1,
"Sending a new inventory does trigger the relay"
);
@ -289,15 +289,15 @@ fn test_inventory_relay() {
alice.receive(
&eve.addr(),
Message::Inventory {
node: eve.id(),
timestamp: now,
inv,
origin: None,
},
);
assert_matches!(
alice.messages(&bob.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(eve.id()) && timestamp == now
Some(Message::Inventory { node, timestamp, .. })
if node == eve.id() && timestamp == now
);
}