use std::{fmt, io, mem};
use crate::crypto;
use crate::git;
use crate::identity::Id;
use crate::node;
use crate::node::Address;
use crate::prelude::BoundedVec;
use crate::service::filter::Filter;
use crate::service::{NodeId, Timestamp};
use crate::wire;
/// Maximum number of addresses which can be announced to other nodes.
pub const ADDRESS_LIMIT: usize = 16;
/// Maximum number of project git references.
pub const REF_LIMIT: usize = 235;
/// Maximum number of inventory which can be announced to other nodes.
pub const INVENTORY_LIMIT: usize = 2973;
#[derive(Debug, Clone, PartialEq, Eq)]
// TODO: We should check the length and charset when deserializing.
pub struct Hostname(String);
impl fmt::Display for Hostname {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[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,
}
impl Subscribe {
pub fn all() -> Self {
Self {
filter: Filter::default(),
since: Timestamp::MIN,
until: Timestamp::MAX,
}
}
}
/// Node announcing itself to the network.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeAnnouncement {
/// Advertized features.
pub features: node::Features,
/// Monotonic timestamp.
pub timestamp: Timestamp,
/// Non-unique alias. Must be valid UTF-8.
pub alias: [u8; 32],
/// Announced addresses.
pub addresses: BoundedVec
,
/// Nonce used for announcement proof-of-work.
pub nonce: u64,
}
impl NodeAnnouncement {
/// Validate a node announcement message.
///
/// Checks that the proof-of-work is valid, by generating a single byte that
/// must be zero.
///
/// `scrypt(encode(announcement)) == 0`
///
pub fn validate(&self) -> bool {
let (n, r, p) = Announcement::POW_PARAMS;
let params = scrypt::Params::new(n, r, p).expect("proof-of-work parameters are valid");
let mut output = [0; 1];
scrypt::scrypt(
wire::serialize(self).as_ref(),
Announcement::POW_SALT,
¶ms,
&mut output,
)
.expect("proof-of-work output vector is a valid length");
output == [0]
}
/// Solve the proof-of-work of a node announcement by iterating through different nonces.
pub fn solve(mut self) -> Self {
loop {
if let Some(nonce) = self.nonce.checked_add(1) {
self.nonce = nonce;
if self.validate() {
break;
}
} else {
// If a very high difficulty is chosen, it's possible to iterate through all
// possible values of the nonce without solving the puzzle. However, with "normal"
// values, this is virtually impossible.
panic!("could not solve proof-of-work!");
}
}
self
}
}
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.encode(writer)?;
n += self.nonce.encode(writer)?;
Ok(n)
}
}
impl wire::Decode for NodeAnnouncement {
fn decode(reader: &mut R) -> Result {
let features = node::Features::decode(reader)?;
let timestamp = Timestamp::decode(reader)?;
let alias = wire::Decode::decode(reader)?;
let addresses = BoundedVec::::decode(reader)?;
let nonce = u64::decode(reader)?;
Ok(Self {
features,
timestamp,
alias,
addresses,
nonce,
})
}
}
/// Node announcing project refs being created or updated.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefsAnnouncement {
/// Repository identifier.
pub id: Id,
/// Updated refs.
pub refs: BoundedVec<(git::RefString, git::Oid), REF_LIMIT>,
/// Time of announcement.
pub timestamp: Timestamp,
}
/// Node announcing its inventory to the network.
/// This should be the whole inventory every time.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InventoryAnnouncement {
/// Node inventory.
pub inventory: BoundedVec,
/// Time of announcement.
pub timestamp: Timestamp,
}
/// Announcement messages are messages that are relayed between peers.
#[derive(Clone, PartialEq, Eq)]
pub enum AnnouncementMessage {
/// Inventory announcement.
Inventory(InventoryAnnouncement),
/// Node announcement.
Node(NodeAnnouncement),
/// Refs announcement.
Refs(RefsAnnouncement),
}
impl AnnouncementMessage {
/// Sign this announcement message.
pub fn signed(self, signer: &G) -> Announcement {
let msg = wire::serialize(&self);
let signature = signer.sign(&msg);
Announcement {
node: *signer.public_key(),
message: self,
signature,
}
}
pub fn timestamp(&self) -> Timestamp {
match self {
Self::Inventory(InventoryAnnouncement { timestamp, .. }) => *timestamp,
Self::Refs(RefsAnnouncement { timestamp, .. }) => *timestamp,
Self::Node(NodeAnnouncement { timestamp, .. }) => *timestamp,
}
}
}
impl From for AnnouncementMessage {
fn from(ann: NodeAnnouncement) -> Self {
Self::Node(ann)
}
}
impl From for AnnouncementMessage {
fn from(ann: InventoryAnnouncement) -> Self {
Self::Inventory(ann)
}
}
impl From for AnnouncementMessage {
fn from(ann: RefsAnnouncement) -> Self {
Self::Refs(ann)
}
}
impl fmt::Debug for AnnouncementMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Node { .. } => write!(f, "Node(..)"),
Self::Inventory(message) => {
write!(
f,
"Inventory([{}], {})",
message
.inventory
.iter()
.map(|i| i.to_string())
.collect::>()
.join(", "),
message.timestamp
)
}
Self::Refs(message) => {
write!(f, "Refs({}, {:?})", message.id, message.refs)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Announcement {
/// Node identifier.
pub node: NodeId,
/// Unsigned node announcement.
pub message: AnnouncementMessage,
/// Signature over the announcement.
pub signature: crypto::Signature,
}
impl Announcement {
/// Proof-of-work parameters for announcements.
///
/// These parameters are fed into `scrypt`.
/// They represent the `log2(N)`, `r`, `p` parameters, respectively.
///
/// * log2(N) – iterations count (affects memory and CPU usage), e.g. 15
/// * r – block size (affects memory and CPU usage), e.g. 8
/// * p – parallelism factor (threads to run in parallel - affects the memory, CPU usage), usually 1
///
/// `15, 8, 1` are usually the recommended parameters.
///
#[cfg(test)]
pub const POW_PARAMS: (u8, u32, u32) = (1, 1, 1);
#[cfg(not(test))]
pub const POW_PARAMS: (u8, u32, u32) = (15, 8, 1);
/// Salt used for generating PoW.
pub const POW_SALT: &[u8] = &[b'r', b'a', b'd'];
/// Verify this announcement's signature.
pub fn verify(&self) -> bool {
let msg = wire::serialize(&self.message);
self.node.verify(msg, &self.signature).is_ok()
}
pub fn matches(&self, filter: &Filter) -> bool {
match &self.message {
AnnouncementMessage::Inventory(_) => true,
AnnouncementMessage::Node(_) => true,
AnnouncementMessage::Refs(RefsAnnouncement { id, .. }) => filter.contains(id),
}
}
}
/// 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 {},
/// Subscribe to gossip messages matching the filter and time range.
Subscribe(Subscribe),
/// Gossip announcement. These messages are relayed to peers, and filtered
/// using [`Message::Subscribe`].
Announcement(Announcement),
/// Ask a connected peer for a Pong.
///
/// Used to check if the remote peer is responsive, or a side-effect free way to keep a
/// connection alive.
Ping(Ping),
/// Response to `Ping` message.
Pong {
/// The pong payload.
zeroes: ZeroBytes,
},
/// Request a session upgrade to the Git protocol and fetch the given repository.
Fetch { rid: Id },
/// Accept a fetch request.
FetchOk { rid: Id },
}
impl Message {
pub fn init() -> Self {
Self::Initialize {}
}
pub fn announcement(
node: NodeId,
message: impl Into,
signature: crypto::Signature,
) -> Self {
Announcement {
node,
signature,
message: message.into(),
}
.into()
}
pub fn node(message: NodeAnnouncement, signer: &G) -> Self {
AnnouncementMessage::from(message).signed(signer).into()
}
pub fn inventory(message: InventoryAnnouncement, signer: &G) -> Self {
AnnouncementMessage::from(message).signed(signer).into()
}
pub fn subscribe(filter: Filter, since: Timestamp, until: Timestamp) -> Self {
Self::Subscribe(Subscribe {
filter,
since,
until,
})
}
}
/// A ping message.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ping {
/// The requested length of the pong message.
pub ponglen: wire::Size,
/// Zero bytes (ignored).
pub zeroes: ZeroBytes,
}
impl Ping {
/// Maximum number of zero bytes in a ping message.
pub const MAX_PING_ZEROES: wire::Size = Message::MAX_SIZE // Message size without the type.
- mem::size_of::() as wire::Size // Account for pong length.
- mem::size_of::() as wire::Size; // Account for zeroes length prefix.
/// Maximum number of zero bytes in a pong message.
pub const MAX_PONG_ZEROES: wire::Size =
Message::MAX_SIZE - mem::size_of::() as wire::Size; // Account for zeroes length
// prefix.
pub fn new(rng: &mut fastrand::Rng) -> Self {
let ponglen = rng.u16(0..Self::MAX_PONG_ZEROES);
Ping {
ponglen,
zeroes: ZeroBytes::new(rng.u16(0..Self::MAX_PING_ZEROES)),
}
}
}
impl From for Message {
fn from(ann: Announcement) -> Self {
Self::Announcement(ann)
}
}
impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Initialize { .. } => write!(f, "Initialize(..)"),
Self::Subscribe(Subscribe { since, until, .. }) => {
write!(f, "Subscribe({since}..{until})")
}
Self::Announcement(Announcement { node, message, .. }) => {
write!(f, "Announcement({node}, {message:?})")
}
Self::Ping(Ping { ponglen, zeroes }) => write!(f, "Ping({ponglen}, {zeroes:?})"),
Self::Pong { zeroes } => write!(f, "Pong({zeroes:?})"),
Self::Fetch { rid } => write!(f, "Fetch({rid})"),
Self::FetchOk { rid } => write!(f, "FetchOk({rid})"),
}
}
}
/// Represents a vector of zeroes of a certain length.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ZeroBytes(wire::Size);
impl ZeroBytes {
pub fn new(size: wire::Size) -> Self {
ZeroBytes(size)
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
pub fn len(&self) -> usize {
self.0.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use crate::wire::Encode;
use crate::crypto::test::signer::MockSigner;
use crate::test::arbitrary;
use fastrand;
use qcheck_macros::quickcheck;
#[test]
fn test_ref_limit() {
let mut refs = Refs::default();
while refs.len() < REF_LIMIT {
refs.insert(arbitrary::refstring(u8::MAX as usize), arbitrary::oid());
}
let bounded_refs = BoundedVec::collect_from(&mut refs.iter().map(|(a, b)| (a.clone(), *b)));
let msg: Message = AnnouncementMessage::from(RefsAnnouncement {
id: arbitrary::gen(1),
refs: bounded_refs,
timestamp: LocalTime::now().as_secs(),
})
.signed(&MockSigner::default())
.into();
let mut buf: Vec = Vec::new();
assert!(
msg.encode(&mut buf).is_ok(),
"REF_LIMIT is too big to support message encoding",
);
let decoded = wire::deserialize(buf.as_slice());
assert!(
decoded.is_ok(),
"REF_LIMIT is too big to support message decoding"
);
assert_eq!(
msg,
decoded.unwrap(),
"encoding and decoding should be safe for message at REF_LIMIT",
);
}
#[test]
fn test_inventory_limit() {
let msg = Message::inventory(
InventoryAnnouncement {
inventory: arbitrary::vec(INVENTORY_LIMIT)
.try_into()
.expect("size within bounds limit"),
timestamp: LocalTime::now().as_secs(),
},
&MockSigner::default(),
);
let mut buf: Vec = Vec::new();
assert!(
msg.encode(&mut buf).is_ok(),
"INVENTORY_LIMIT is a valid limit for encoding",
);
let decoded = wire::deserialize(buf.as_slice());
assert!(
decoded.is_ok(),
"INVENTORY_LIMIT is a valid limit for decoding"
);
assert_eq!(
msg,
decoded.unwrap(),
"encoding and decoding should be safe for message at INVENTORY_LIMIT",
);
}
#[quickcheck]
fn prop_refs_announcement_signing(id: Id, refs: Refs) {
let signer = MockSigner::new(&mut fastrand::Rng::new());
let timestamp = 0;
let message = AnnouncementMessage::Refs(RefsAnnouncement {
id,
refs: BoundedVec::collect_from(&mut refs.iter().map(|(k, v)| (k.clone(), *v))),
timestamp,
});
let ann = message.signed(&signer);
assert!(ann.verify());
}
#[test]
fn test_node_announcement_validate() {
let ann = NodeAnnouncement {
features: node::Features::SEED,
timestamp: 42491841,
alias: [0; 32],
addresses: BoundedVec::new(),
nonce: 0,
};
assert!(!ann.validate());
assert!(ann.solve().validate());
}
}