use std::{fmt, io, net}; use crate::crypto; use crate::git; use crate::identity::Id; use crate::service::filter::Filter; use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION}; use crate::storage::refs::Refs; use crate::wire; /// Message envelope. All messages sent over the network are wrapped in this type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Envelope { /// Network magic constant. Used to differentiate networks. pub magic: u32, /// The message payload. pub msg: Message, } /// Advertized node feature. Signals what services the node supports. pub type NodeFeatures = [u8; 32]; #[derive(Debug, Clone, PartialEq, Eq)] // TODO: We should check the length and charset when deserializing. pub struct Hostname(String); /// Peer public protocol address. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Address { Ipv4 { ip: net::Ipv4Addr, port: u16, }, Ipv6 { ip: net::Ipv6Addr, port: u16, }, Hostname { host: Hostname, port: u16, }, /// Tor V3 onion address. Onion { key: crypto::PublicKey, port: u16, checksum: u16, version: u8, }, } impl From for Address { fn from(other: net::SocketAddr) -> Self { let port = other.port(); match other.ip() { net::IpAddr::V4(ip) => Self::Ipv4 { ip, port }, net::IpAddr::V6(ip) => Self::Ipv6 { ip, port }, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Subscribe { /// Subscribe to events matching this filter. pub filter: Filter, /// Request messages since this time. pub since: Timestamp, /// Request messages until this time. pub until: Timestamp, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct NodeAnnouncement { /// Advertized features. pub features: NodeFeatures, /// Monotonic timestamp. pub timestamp: Timestamp, /// Non-unique alias. Must be valid UTF-8. pub alias: [u8; 32], /// Announced addresses. pub addresses: Vec
, } impl NodeAnnouncement { /// Verify a signature on this message. pub fn verify(&self, signer: &NodeId, signature: &crypto::Signature) -> bool { let msg = wire::serialize(self); signer.verify(signature, &msg).is_ok() } } impl wire::Encode for NodeAnnouncement { fn encode(&self, writer: &mut W) -> Result { let mut n = 0; n += self.features.encode(writer)?; n += self.timestamp.encode(writer)?; n += self.alias.encode(writer)?; n += self.addresses.as_slice().encode(writer)?; Ok(n) } } impl wire::Decode for NodeAnnouncement { fn decode(reader: &mut R) -> Result { let features = NodeFeatures::decode(reader)?; let timestamp = Timestamp::decode(reader)?; let alias = wire::Decode::decode(reader)?; let addresses = Vec::
::decode(reader)?; Ok(Self { features, timestamp, alias, addresses, }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct RefsAnnouncement { /// Repository identifier. pub id: Id, /// Updated refs. pub refs: Refs, } impl RefsAnnouncement { /// Verify a signature on this message. pub fn verify(&self, signer: &NodeId, signature: &crypto::Signature) -> bool { let msg = wire::serialize(self); signer.verify(signature, &msg).is_ok() } /// Sign this announcement. pub fn sign(&self, signer: S) -> crypto::Signature { let msg = wire::serialize(self); signer.sign(&msg) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct InventoryAnnouncement { pub inventory: Vec, pub timestamp: Timestamp, } impl InventoryAnnouncement { /// Verify a signature on this message. pub fn verify(&self, signer: NodeId, signature: &crypto::Signature) -> bool { let msg = wire::serialize(self); signer.verify(signature, &msg).is_ok() } } /// Message payload. /// These are the messages peers send to each other. #[derive(Clone, PartialEq, Eq)] pub enum Message { /// The first message sent to a peer after connection. Initialize { // TODO: This is currently untrusted. id: NodeId, timestamp: Timestamp, version: u32, addrs: Vec
, git: git::Url, }, /// Subscribe to gossip messages matching the filter and time range. /// timestamp. Subscribe(Subscribe), /// Node announcing its inventory to the network. /// This should be the whole inventory every time. InventoryAnnouncement { /// Node identifier. node: NodeId, /// Unsigned node inventory. message: InventoryAnnouncement, /// Signature over the announcement. signature: crypto::Signature, }, /// Node announcing itself to the network. NodeAnnouncement { /// Node identifier. node: NodeId, /// Unsigned node announcement. message: NodeAnnouncement, /// Signature over the announcement, by the node being announced. signature: crypto::Signature, }, /// Node announcing project refs being created or updated. RefsAnnouncement { /// Node identifier. node: NodeId, /// Unsigned refs announcement. message: RefsAnnouncement, /// Signature over the announcement, by the node that updated the refs. signature: crypto::Signature, }, } impl Message { pub fn init(id: NodeId, timestamp: Timestamp, addrs: Vec
, git: git::Url) -> Self { Self::Initialize { id, timestamp, version: PROTOCOL_VERSION, addrs, git, } } pub fn node(message: NodeAnnouncement, signer: S) -> Self { let msg = wire::serialize(&message); let signature = signer.sign(&msg); let node = *signer.public_key(); Self::NodeAnnouncement { node, signature, message, } } pub fn inventory(message: InventoryAnnouncement, signer: S) -> Self { let msg = wire::serialize(&message); let signature = signer.sign(&msg); let node = *signer.public_key(); Self::InventoryAnnouncement { node, signature, message, } } pub fn subscribe(filter: Filter, since: Timestamp, until: Timestamp) -> Self { Self::Subscribe(Subscribe { filter, since, until, }) } } impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Initialize { id, .. } => write!(f, "Initialize({})", id), Self::Subscribe(Subscribe { since, until, .. }) => { write!(f, "Subscribe({}..{})", since, until) } Self::NodeAnnouncement { node, .. } => write!(f, "NodeAnnouncement({})", node), Self::InventoryAnnouncement { node, message, .. } => { write!( f, "InventoryAnnouncement({}, [{}], {})", node, message .inventory .iter() .map(|i| i.to_string()) .collect::>() .join(", "), message.timestamp ) } Self::RefsAnnouncement { node, message, .. } => { write!( f, "RefsAnnouncement({}, {}, {:?})", node, message.id, message.refs ) } } } } #[cfg(test)] mod tests { use super::*; use quickcheck_macros::quickcheck; use crate::crypto::Signer; use crate::test::crypto::MockSigner; #[quickcheck] fn prop_refs_announcement_signing(id: Id, refs: Refs) { let signer = MockSigner::new(&mut fastrand::Rng::new()); let message = RefsAnnouncement { id, refs }; let signature = message.sign(&signer); assert!(message.verify(signer.public_key(), &signature)); } }