node: Implement binary encoding for messages
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
c7f78eafb6
commit
b3d3077ccc
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue