From b3d3077ccc23cb994fd84b2d148fd12ead2c2679 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Thu, 8 Sep 2022 12:09:44 +0200 Subject: [PATCH] node: Implement binary encoding for messages Signed-off-by: Alexis Sellier --- node/Cargo.toml | 1 + node/src/crypto.rs | 29 ++- node/src/git.rs | 1 + node/src/hash.rs | 58 ++---- node/src/identity.rs | 32 +++- node/src/protocol.rs | 15 +- node/src/protocol/message.rs | 183 ++++++++++++++++-- node/src/protocol/peer.rs | 10 +- node/src/protocol/wire.rs | 348 +++++++++++++++++++++++++++++++++++ node/src/storage/refs.rs | 25 +++ node/src/test/tests.rs | 24 +-- 11 files changed, 638 insertions(+), 88 deletions(-) create mode 100644 node/src/protocol/wire.rs diff --git a/node/Cargo.toml b/node/Cargo.toml index 3d1892e8..a0727abc 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -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" } diff --git a/node/src/crypto.rs b/node/src/crypto.rs index ea46d528..40bab804 100644 --- a/node/src/crypto.rs +++ b/node/src/crypto.rs @@ -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 for String { fn from(other: PublicKey) -> Self { - other.encode() + other.to_human() } } @@ -96,8 +96,16 @@ impl From for PublicKey { } } +impl TryFrom<[u8; 32]> for PublicKey { + type Error = ed25519::Error; + + fn try_from(other: [u8; 32]) -> Result { + 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); + } +} diff --git a/node/src/git.rs b/node/src/git.rs index 87d652ec..786898b4 100644 --- a/node/src/git.rs +++ b/node/src/git.rs @@ -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; diff --git a/node/src/hash.rs b/node/src/hash.rs index 55ef5d12..8715c4d4 100644 --- a/node/src/hash.rs +++ b/node/src/hash.rs @@ -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 { - let (_, bytes) = multibase::decode(s)?; - let array = bytes - .try_into() - .map_err(|v: Vec| DecodeError::InvalidLength(v.len()))?; - - Ok(Self(array)) - } } -impl FromStr for Digest { - type Err = DecodeError; - - fn from_str(s: &str) -> Result { - 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 { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| DecodeError::InvalidLength(bytes.len()))?; + + Ok(bytes.into()) + } +} + impl From::OutputSize>> for Digest { fn from(array: GenericArray::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); - } -} diff --git a/node/src/identity.rs b/node/src/identity.rs index 0b299256..5c69e983 100644 --- a/node/src/identity.rs +++ b/node/src/identity.rs @@ -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 { + 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 { - Ok(Self(hash::Digest::from_str(s)?)) + Self::from_human(s) } } @@ -66,6 +76,14 @@ impl From for Id { } } +impl Deref for Id { + type Target = hash::Digest; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl serde::Serialize for Id { fn serialize(&self, serializer: S) -> Result 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 { @@ -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); } diff --git a/node/src/protocol.rs b/node/src/protocol.rs index 669ae1f7..95f58b91 100644 --- a/node/src/protocol.rs +++ b/node/src/protocol.rs @@ -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 { rng: Rng, } +impl Context +where + T: storage::ReadStorage, + G: crypto::Signer, +{ + pub(crate) fn id(&self) -> NodeId { + *self.signer.public_key() + } +} + impl<'r, S, T, G> Context 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 { diff --git a/node/src/protocol/message.rs b/node/src/protocol/message.rs index 41d4481a..5f9e00ae 100644 --- a/node/src/protocol/message.rs +++ b/node/src/protocol/message.rs @@ -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(&self, writer: &mut W) -> Result { + 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(reader: &mut R) -> Result { + 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
, - 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, timestamp: Timestamp, - /// Original peer this inventory came from. We don't set this when we - /// are the originator, only when relaying. - origin: Option, }, /// 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
, git: Url) -> Self { + pub fn hello(id: NodeId, timestamp: Timestamp, addrs: Vec
, git: git::Url) -> Self { Self::Hello { id, timestamp, @@ -133,18 +177,127 @@ impl Message { pub fn inventory(ctx: &Context) -> Result 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>) -> 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(&self, writer: &mut W) -> Result { + 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(reader: &mut R) -> Result { + use byteorder::ReadBytesExt; + + let type_id = reader.read_u16::()?; + + match type_id { + 0 => { + let id = NodeId::decode(reader)?; + let timestamp = Timestamp::decode(reader)?; + let version = u32::decode(reader)?; + let addrs = Vec::
::decode(reader)?; + let git = git::Url::decode(reader)?; + + Ok(Self::Hello { + id, + timestamp, + version, + addrs, + git, + }) + } + 2 => { + todo!(); + } + 4 => { + let ids = Vec::::decode(reader)?; + + Ok(Self::GetInventory { ids }) + } + 6 => { + let node = NodeId::decode(reader)?; + let inv = Vec::::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!(); + } + } + } } diff --git a/node/src/protocol/peer.rs b/node/src/protocol/peer.rs index 2688ba14..47dfc2ee 100644 --- a/node/src/protocol/peer.rs +++ b/node/src/protocol/peer.rs @@ -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, })); } } diff --git a/node/src/protocol/wire.rs b/node/src/protocol/wire.rs new file mode 100644 index 00000000..07bfb08b --- /dev/null +++ b/node/src/protocol/wire.rs @@ -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(&self, writer: &mut W) -> Result; +} + +pub trait Decode: Sized { + fn decode(reader: &mut R) -> Result; +} + +/// Encode an object into a vector. +pub fn serialize(data: &T) -> Vec { + 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(&self, writer: &mut W) -> Result { + writer.write_u8(*self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for u16 { + fn encode(&self, writer: &mut W) -> Result { + writer.write_u16::(*self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for u32 { + fn encode(&self, writer: &mut W) -> Result { + writer.write_u32::(*self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for u64 { + fn encode(&self, writer: &mut W) -> Result { + writer.write_u64::(*self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for usize { + fn encode(&self, writer: &mut W) -> Result { + let u = (*self) + .try_into() + .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?; + + writer.write_u32::(u)?; + + Ok(mem::size_of_val(&u)) + } +} + +impl Encode for PublicKey { + fn encode(&self, writer: &mut W) -> Result { + self.as_bytes().encode(writer) + } +} + +impl Encode for &[u8; T] { + fn encode(&self, writer: &mut W) -> Result { + writer.write_all(*self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for [u8; T] { + fn encode(&self, writer: &mut W) -> Result { + writer.write_all(self)?; + + Ok(mem::size_of::()) + } +} + +impl Encode for &[T] +where + T: Encode, +{ + fn encode(&self, writer: &mut W) -> Result { + 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(&self, writer: &mut W) -> Result { + match self { + net::IpAddr::V4(addr) => addr.octets().encode(writer), + net::IpAddr::V6(addr) => addr.octets().encode(writer), + } + } +} + +impl Encode for str { + fn encode(&self, writer: &mut W) -> Result { + let mut n = 0; + + n += self.len().encode(writer)?; + n += self.as_bytes().encode(writer)?; + + Ok(n) + } +} + +impl Encode for String { + fn encode(&self, writer: &mut W) -> Result { + self.as_str().encode(writer) + } +} + +impl Encode for git::Url { + fn encode(&self, writer: &mut W) -> Result { + self.to_string().encode(writer) + } +} + +impl Encode for Digest { + fn encode(&self, writer: &mut W) -> Result { + self.as_ref().encode(writer) + } +} + +impl Encode for Id { + fn encode(&self, writer: &mut W) -> Result { + self.deref().encode(writer) + } +} + +impl Encode for Refs { + fn encode(&self, writer: &mut W) -> Result { + 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(&self, writer: &mut W) -> Result { + self.to_bytes().encode(writer) + } +} + +impl Encode for git::Oid { + fn encode(&self, writer: &mut W) -> Result { + // Nb. We use length-encoding here to support future SHA-2 object ids. + self.as_bytes().encode(writer) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +impl Decode for PublicKey { + fn decode(reader: &mut R) -> Result { + 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(reader: &mut R) -> Result { + 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(reader: &mut R) -> Result { + let len = usize::decode(reader)?; + #[allow(non_upper_case_globals)] + const expected: usize = mem::size_of::(); + + 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(reader: &mut R) -> Result { + let bytes: [u8; 64] = Decode::decode(reader)?; + + Ok(Signature::from(bytes)) + } +} + +impl Decode for u8 { + fn decode(reader: &mut R) -> Result { + reader.read_u8().map_err(Error::from) + } +} + +impl Decode for u16 { + fn decode(reader: &mut R) -> Result { + reader.read_u16::().map_err(Error::from) + } +} + +impl Decode for u32 { + fn decode(reader: &mut R) -> Result { + reader.read_u32::().map_err(Error::from) + } +} + +impl Decode for u64 { + fn decode(reader: &mut R) -> Result { + reader.read_u64::().map_err(Error::from) + } +} + +impl Decode for usize { + fn decode(reader: &mut R) -> Result { + let size: usize = u64::decode(reader)? + .try_into() + .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?; + + Ok(size) + } +} + +impl Decode for [u8; N] { + fn decode(reader: &mut R) -> Result { + let mut ary = [0; N]; + reader.read_exact(&mut ary)?; + + Ok(ary) + } +} + +impl Decode for Vec +where + T: Decode, +{ + fn decode(reader: &mut R) -> Result { + 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(reader: &mut R) -> Result { + 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(reader: &mut R) -> Result { + 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(reader: &mut R) -> Result { + let digest: Digest = Decode::decode(reader)?; + + Ok(Self::from(digest)) + } +} + +impl Decode for Digest { + fn decode(reader: &mut R) -> Result { + let bytes: [u8; 32] = Decode::decode(reader)?; + + Ok(Self::from(bytes)) + } +} diff --git a/node/src/storage/refs.rs b/node/src/storage/refs.rs index 24a9710a..5fe228a5 100644 --- a/node/src/storage/refs.rs +++ b/node/src/storage/refs.rs @@ -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 { } } +impl wire::Encode for SignedRefs { + fn encode(&self, writer: &mut W) -> Result { + let mut n = 0; + + n += self.refs.encode(writer)?; + n += self.signature.encode(writer)?; + + Ok(n) + } +} + +impl wire::Decode for SignedRefs { + fn decode(reader: &mut R) -> Result { + let refs = Refs::decode(reader)?; + let signature = Signature::decode(reader)?; + + Ok(Self { + refs, + signature, + _verified: PhantomData, + }) + } +} + impl Deref for SignedRefs { type Target = Refs; diff --git a/node/src/test/tests.rs b/node/src/test/tests.rs index 58b7e98f..a33a0b55 100644 --- a/node/src/test/tests.rs +++ b/node/src/test/tests.rs @@ -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 ); }