Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Service -> Protocol
Alexis Sellier committed 3 years ago
commit 15e8a96948aca10c5a03e9f1acdb24f908cc730f
parent 1017a2bc69d6bb1a42a3c35c52686cc9b1a2496f
24 files changed +2727 -2727
modified node/src/client.rs
@@ -7,8 +7,8 @@ use nakamoto_net::{LocalTime, Reactor};
use crate::clock::RefClock;
use crate::collections::HashMap;
use crate::crypto::Signer;
-
use crate::protocol;
-
use crate::protocol::wire::Wire;
+
use crate::service;
+
use crate::service::wire::Wire;
use crate::storage::git::Storage;
use crate::transport::Transport;

@@ -17,19 +17,19 @@ pub mod handle;
/// Client configuration.
#[derive(Debug, Clone)]
pub struct Config {
-
    /// Client protocol configuration.
-
    pub protocol: protocol::Config,
+
    /// Client service configuration.
+
    pub service: service::Config,
    /// Client listen addresses.
    pub listen: Vec<net::SocketAddr>,
}

impl Config {
    /// Create a new configuration for the given network.
-
    pub fn new(network: protocol::Network) -> Self {
+
    pub fn new(network: service::Network) -> Self {
        Self {
-
            protocol: protocol::Config {
+
            service: service::Config {
                network,
-
                ..protocol::Config::default()
+
                ..service::Config::default()
            },
            ..Self::default()
        }
@@ -39,7 +39,7 @@ impl Config {
impl Default for Config {
    fn default() -> Self {
        Self {
-
            protocol: protocol::Config::default(),
+
            service: service::Config::default(),
            listen: vec![([0, 0, 0, 0], 0).into()],
        }
    }
@@ -50,8 +50,8 @@ pub struct Client<R: Reactor, G: Signer> {
    storage: Storage,
    signer: G,

-
    handle: chan::Sender<protocol::Command>,
-
    commands: chan::Receiver<protocol::Command>,
+
    handle: chan::Sender<service::Command>,
+
    commands: chan::Receiver<service::Command>,
    shutdown: chan::Sender<()>,
    listening: chan::Receiver<net::SocketAddr>,
    events: Events,
@@ -59,7 +59,7 @@ pub struct Client<R: Reactor, G: Signer> {

impl<R: Reactor, G: Signer> Client<R, G> {
    pub fn new<P: AsRef<Path>>(path: P, signer: G) -> Result<Self, nakamoto_net::error::Error> {
-
        let (handle, commands) = chan::unbounded::<protocol::Command>();
+
        let (handle, commands) = chan::unbounded::<service::Command>();
        let (shutdown, shutdown_recv) = chan::bounded(1);
        let (listening_send, listening) = chan::bounded(1);
        let reactor = R::new(shutdown_recv, listening_send)?;
@@ -79,7 +79,7 @@ impl<R: Reactor, G: Signer> Client<R, G> {
    }

    pub fn run(mut self, config: Config) -> Result<(), nakamoto_net::error::Error> {
-
        let network = config.protocol.network;
+
        let network = config.service.network;
        let rng = fastrand::Rng::new();
        let time = LocalTime::now();
        let storage = self.storage;
@@ -88,8 +88,8 @@ impl<R: Reactor, G: Signer> Client<R, G> {

        log::info!("Initializing client ({:?})..", network);

-
        let protocol = protocol::Protocol::new(
-
            config.protocol,
+
        let service = service::Service::new(
+
            config.service,
            RefClock::from(time),
            storage,
            addresses,
@@ -98,7 +98,7 @@ impl<R: Reactor, G: Signer> Client<R, G> {
        );
        self.reactor.run(
            &config.listen,
-
            Transport::new(Wire::new(protocol)),
+
            Transport::new(Wire::new(service)),
            self.events,
            self.commands,
        )?;
@@ -119,8 +119,8 @@ impl<R: Reactor, G: Signer> Client<R, G> {

pub struct Events {}

-
impl nakamoto_net::Publisher<protocol::Event> for Events {
-
    fn publish(&mut self, e: protocol::Event) {
+
impl nakamoto_net::Publisher<service::Event> for Events {
+
    fn publish(&mut self, e: service::Event) {
        log::info!("Received event {:?}", e);
    }
}
modified node/src/client/handle.rs
@@ -5,8 +5,8 @@ use nakamoto_net::Waker;
use thiserror::Error;

use crate::identity::Id;
-
use crate::protocol;
-
use crate::protocol::{CommandError, FetchLookup};
+
use crate::service;
+
use crate::service::{CommandError, FetchLookup};

/// An error resulting from a handle method.
#[derive(Error, Debug)]
@@ -47,7 +47,7 @@ impl<T> From<chan::SendError<T>> for Error {
}

pub struct Handle<W: Waker> {
-
    pub(crate) commands: chan::Sender<protocol::Command>,
+
    pub(crate) commands: chan::Sender<service::Command>,
    pub(crate) shutdown: chan::Sender<()>,
    pub(crate) listening: chan::Receiver<net::SocketAddr>,
    pub(crate) waker: W,
@@ -57,31 +57,31 @@ impl<W: Waker> traits::Handle for Handle<W> {
    /// Retrieve or update the given project from the network.
    fn fetch(&self, id: Id) -> Result<FetchLookup, Error> {
        let (sender, receiver) = chan::bounded(1);
-
        self.commands.send(protocol::Command::Fetch(id, sender))?;
+
        self.commands.send(service::Command::Fetch(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Start tracking the given project. Doesn't do anything if the project is already tracked.
    fn track(&self, id: Id) -> Result<bool, Error> {
        let (sender, receiver) = chan::bounded(1);
-
        self.commands.send(protocol::Command::Track(id, sender))?;
+
        self.commands.send(service::Command::Track(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Untrack the given project and delete it from storage.
    fn untrack(&self, id: Id) -> Result<bool, Error> {
        let (sender, receiver) = chan::bounded(1);
-
        self.commands.send(protocol::Command::Untrack(id, sender))?;
+
        self.commands.send(service::Command::Untrack(id, sender))?;
        receiver.recv().map_err(Error::from)
    }

    /// Notify the client that a project has been updated.
    fn updated(&self, id: Id) -> Result<(), Error> {
-
        self.command(protocol::Command::AnnounceRefs(id))
+
        self.command(service::Command::AnnounceRefs(id))
    }

    /// Send a command to the command channel, and wake up the event loop.
-
    fn command(&self, cmd: protocol::Command) -> Result<(), Error> {
+
    fn command(&self, cmd: service::Command) -> Result<(), Error> {
        self.commands.send(cmd)?;
        self.waker.wake()?;

@@ -111,7 +111,7 @@ pub mod traits {
        /// Notify the client that a project has been updated.
        fn updated(&self, id: Id) -> Result<(), Error>;
        /// Send a command to the command channel, and wake up the event loop.
-
        fn command(&self, cmd: protocol::Command) -> Result<(), Error>;
+
        fn command(&self, cmd: service::Command) -> Result<(), Error>;
        /// Ask the client to shutdown.
        fn shutdown(self) -> Result<(), Error>;
    }
modified node/src/control.rs
@@ -10,8 +10,8 @@ use std::{fs, io, net};
use crate::client;
use crate::client::handle::traits::Handle;
use crate::identity::Id;
-
use crate::protocol::FetchLookup;
-
use crate::protocol::FetchResult;
+
use crate::service::FetchLookup;
+
use crate::service::FetchResult;

/// Default name for control socket file.
pub const DEFAULT_SOCKET_NAME: &str = "radicle.sock";
modified node/src/decoder.rs
@@ -1,8 +1,8 @@
use std::io;
use std::marker::PhantomData;

-
use crate::protocol::message::Envelope;
-
use crate::protocol::wire;
+
use crate::service::message::Envelope;
+
use crate::service::wire;

/// Message stream decoder.
///
modified node/src/lib.rs
@@ -14,9 +14,9 @@ mod git;
mod hash;
mod identity;
mod logger;
-
mod protocol;
mod rad;
mod serde_ext;
+
mod service;
mod storage;
#[cfg(test)]
mod test;
deleted node/src/protocol.rs
@@ -1,888 +0,0 @@
-
#![allow(dead_code)]
-
pub mod config;
-
pub mod filter;
-
pub mod message;
-
pub mod peer;
-
pub mod wire;
-

-
use std::ops::{Deref, DerefMut};
-
use std::{collections::VecDeque, fmt, net, net::IpAddr};
-

-
use crossbeam_channel as chan;
-
use fastrand::Rng;
-
use git_url::Url;
-
use log::*;
-
use nakamoto::{LocalDuration, LocalTime};
-
use nakamoto_net as nakamoto;
-
use nakamoto_net::Link;
-
use nonempty::NonEmpty;
-

-
use crate::address_book;
-
use crate::address_book::AddressBook;
-
use crate::address_manager::AddressManager;
-
use crate::clock::RefClock;
-
use crate::collections::{HashMap, HashSet};
-
use crate::crypto;
-
use crate::identity::{Id, Project};
-
use crate::protocol::config::ProjectTracking;
-
use crate::protocol::message::{NodeAnnouncement, RefsAnnouncement};
-
use crate::protocol::peer::{Peer, PeerError, PeerState};
-
use crate::storage;
-
use crate::storage::{Inventory, ReadRepository, RefUpdate, WriteRepository, WriteStorage};
-

-
pub use crate::protocol::config::{Config, Network};
-
pub use crate::protocol::message::{Envelope, Message};
-

-
use self::filter::Filter;
-
use self::message::{InventoryAnnouncement, NodeFeatures};
-

-
pub const DEFAULT_PORT: u16 = 8776;
-
pub const PROTOCOL_VERSION: u32 = 1;
-
pub const TARGET_OUTBOUND_PEERS: usize = 8;
-
pub const IDLE_INTERVAL: LocalDuration = LocalDuration::from_secs(30);
-
pub const ANNOUNCE_INTERVAL: LocalDuration = LocalDuration::from_secs(30);
-
pub const SYNC_INTERVAL: LocalDuration = LocalDuration::from_secs(60);
-
pub const PRUNE_INTERVAL: LocalDuration = LocalDuration::from_mins(30);
-
pub const MAX_CONNECTION_ATTEMPTS: usize = 3;
-
pub const MAX_TIME_DELTA: LocalDuration = LocalDuration::from_mins(60);
-

-
/// Network node identifier.
-
pub type NodeId = crypto::PublicKey;
-
/// Network routing table. Keeps track of where projects are hosted.
-
pub type Routing = HashMap<Id, HashSet<NodeId>>;
-
/// Seconds since epoch.
-
pub type Timestamp = u64;
-

-
/// Output of a state transition.
-
#[derive(Debug)]
-
pub enum Io {
-
    /// There are some messages ready to be sent to a peer.
-
    Write(net::SocketAddr, Vec<Envelope>),
-
    /// Connect to a peer.
-
    Connect(net::SocketAddr),
-
    /// Disconnect from a peer.
-
    Disconnect(net::SocketAddr, DisconnectReason),
-
    /// Ask for a wakeup in a specified amount of time.
-
    Wakeup(LocalDuration),
-
    /// Emit an event.
-
    Event(Event),
-
}
-

-
/// A protocol event.
-
#[derive(Debug, Clone)]
-
pub enum Event {
-
    RefsFetched {
-
        from: Url,
-
        project: Id,
-
        updated: Vec<RefUpdate>,
-
    },
-
}
-

-
/// Error returned by [`Command::Fetch`].
-
#[derive(thiserror::Error, Debug)]
-
pub enum FetchError {
-
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
    #[error(transparent)]
-
    Storage(#[from] storage::Error),
-
    #[error(transparent)]
-
    Fetch(#[from] storage::FetchError),
-
}
-

-
/// Result of looking up seeds in our routing table.
-
#[derive(Debug)]
-
pub enum FetchLookup {
-
    /// Found seeds for the given project.
-
    Found {
-
        seeds: NonEmpty<net::SocketAddr>,
-
        results: chan::Receiver<FetchResult>,
-
    },
-
    /// Can't fetch because no seeds were found for this project.
-
    NotFound,
-
    /// Can't fetch because the project isn't tracked.
-
    NotTracking,
-
    /// Error trying to find seeds.
-
    Error(FetchError),
-
}
-

-
/// Result of a fetch request from a specific seed.
-
#[derive(Debug)]
-
#[allow(clippy::large_enum_variant)]
-
pub enum FetchResult {
-
    /// Successful fetch from a seed.
-
    Fetched {
-
        from: net::SocketAddr,
-
        updated: Vec<RefUpdate>,
-
    },
-
    /// Error fetching the resource from a seed.
-
    Error {
-
        from: net::SocketAddr,
-
        error: FetchError,
-
    },
-
}
-

-
/// Commands sent to the protocol by the operator.
-
#[derive(Debug)]
-
pub enum Command {
-
    AnnounceRefs(Id),
-
    Connect(net::SocketAddr),
-
    Fetch(Id, chan::Sender<FetchLookup>),
-
    Track(Id, chan::Sender<bool>),
-
    Untrack(Id, chan::Sender<bool>),
-
}
-

-
/// Command-related errors.
-
#[derive(thiserror::Error, Debug)]
-
pub enum CommandError {}
-

-
#[derive(Debug)]
-
pub struct Protocol<S, T, G> {
-
    /// Peers currently or recently connected.
-
    peers: Peers,
-
    /// Protocol state that isn't peer-specific.
-
    context: Context<S, T, G>,
-
    /// Whether our local inventory no long represents what we have announced to the network.
-
    out_of_sync: bool,
-
    /// Last time the protocol was idle.
-
    last_idle: LocalTime,
-
    /// Last time the protocol synced.
-
    last_sync: LocalTime,
-
    /// Last time the protocol routing table was pruned.
-
    last_prune: LocalTime,
-
    /// Last time the protocol announced its inventory.
-
    last_announce: LocalTime,
-
    /// Time when the protocol was initialized.
-
    start_time: LocalTime,
-
}
-

-
impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protocol<S, T, G> {
-
    pub fn new(
-
        config: Config,
-
        clock: RefClock,
-
        storage: T,
-
        addresses: S,
-
        signer: G,
-
        rng: Rng,
-
    ) -> Self {
-
        let addrmgr = AddressManager::new(addresses);
-

-
        Self {
-
            context: Context::new(config, clock, storage, addrmgr, signer, rng.clone()),
-
            peers: Peers::new(rng),
-
            out_of_sync: false,
-
            last_idle: LocalTime::default(),
-
            last_sync: LocalTime::default(),
-
            last_prune: LocalTime::default(),
-
            last_announce: LocalTime::default(),
-
            start_time: LocalTime::default(),
-
        }
-
    }
-

-
    pub fn disconnect(&mut self, remote: &IpAddr, reason: DisconnectReason) {
-
        if let Some(addr) = self.peers.get(remote).map(|p| p.addr) {
-
            self.context.disconnect(addr, reason);
-
        }
-
    }
-

-
    pub fn seeds(&self, id: &Id) -> Box<dyn Iterator<Item = (&NodeId, &Peer)> + '_> {
-
        if let Some(peers) = self.routing.get(id) {
-
            Box::new(
-
                peers
-
                    .iter()
-
                    .filter_map(|id| self.peers.by_id(id).map(|p| (id, p))),
-
            )
-
        } else {
-
            Box::new(std::iter::empty())
-
        }
-
    }
-

-
    pub fn tracked(&self) -> Result<Vec<Id>, storage::Error> {
-
        let tracked = match &self.config.project_tracking {
-
            ProjectTracking::All { blocked } => self
-
                .storage
-
                .inventory()?
-
                .into_iter()
-
                .filter(|id| !blocked.contains(id))
-
                .collect(),
-

-
            ProjectTracking::Allowed(projs) => projs.iter().cloned().collect(),
-
        };
-

-
        Ok(tracked)
-
    }
-

-
    /// Track a project.
-
    /// Returns whether or not the tracking policy was updated.
-
    pub fn track(&mut self, id: Id) -> bool {
-
        self.out_of_sync = self.config.track(id);
-
        self.out_of_sync
-
    }
-

-
    /// Untrack a project.
-
    /// Returns whether or not the tracking policy was updated.
-
    /// Note that when untracking, we don't announce anything to the network. This is because by
-
    /// simply not announcing it anymore, it will eventually be pruned by nodes.
-
    pub fn untrack(&mut self, id: Id) -> bool {
-
        self.config.untrack(id)
-
    }
-

-
    /// Find the closest `n` peers by proximity in tracking graphs.
-
    /// Returns a sorted list from the closest peer to the furthest.
-
    /// Peers with more trackings in common score score higher.
-
    #[allow(unused)]
-
    pub fn closest_peers(&self, n: usize) -> Vec<NodeId> {
-
        todo!()
-
    }
-

-
    /// Get the connected peers.
-
    pub fn peers(&self) -> &Peers {
-
        &self.peers
-
    }
-

-
    /// Get the current inventory.
-
    pub fn inventory(&self) -> Result<Inventory, storage::Error> {
-
        self.context.storage.inventory()
-
    }
-

-
    /// Get the storage instance.
-
    pub fn storage(&self) -> &T {
-
        &self.context.storage
-
    }
-

-
    /// Get the mutable storage instance.
-
    pub fn storage_mut(&mut self) -> &mut T {
-
        &mut self.context.storage
-
    }
-

-
    /// Get a project from storage, using the local node's key.
-
    pub fn get(&self, proj: &Id) -> Result<Option<Project>, storage::Error> {
-
        self.storage.get(&self.node_id(), proj)
-
    }
-

-
    /// Get the local signer.
-
    pub fn signer(&self) -> &G {
-
        &self.context.signer
-
    }
-

-
    /// Get the local protocol time.
-
    pub fn local_time(&self) -> LocalTime {
-
        self.context.clock.local_time()
-
    }
-

-
    /// Get protocol configuration.
-
    pub fn config(&self) -> &Config {
-
        &self.context.config
-
    }
-

-
    /// Get reference to routing table.
-
    pub fn routing(&self) -> &Routing {
-
        &self.context.routing
-
    }
-

-
    /// Get I/O outbox.
-
    pub fn outbox(&mut self) -> &mut VecDeque<Io> {
-
        &mut self.context.io
-
    }
-

-
    pub fn lookup(&self, id: &Id) -> Lookup {
-
        Lookup {
-
            local: self.context.storage.get(&self.node_id(), id).unwrap(),
-
            remote: self
-
                .context
-
                .routing
-
                .get(id)
-
                .map_or(vec![], |r| r.iter().cloned().collect()),
-
        }
-
    }
-

-
    pub fn initialize(&mut self, time: LocalTime) {
-
        trace!("Init {}", time.as_secs());
-

-
        self.start_time = time;
-

-
        // Connect to configured peers.
-
        let addrs = self.context.config.connect.clone();
-
        for addr in addrs {
-
            self.context.connect(addr);
-
        }
-
    }
-

-
    pub fn tick(&mut self, now: nakamoto::LocalTime) {
-
        trace!("Tick +{}", now - self.start_time);
-

-
        self.context.clock.set(now);
-
    }
-

-
    pub fn wake(&mut self) {
-
        let now = self.context.clock.local_time();
-

-
        trace!("Wake +{}", now - self.start_time);
-

-
        if now - self.last_idle >= IDLE_INTERVAL {
-
            debug!("Running 'idle' task...");
-

-
            self.maintain_connections();
-
            self.context.io.push_back(Io::Wakeup(IDLE_INTERVAL));
-
            self.last_idle = now;
-
        }
-
        if now - self.last_sync >= SYNC_INTERVAL {
-
            debug!("Running 'sync' task...");
-

-
            // TODO: What do we do here?
-
            self.context.io.push_back(Io::Wakeup(SYNC_INTERVAL));
-
            self.last_sync = now;
-
        }
-
        if now - self.last_announce >= ANNOUNCE_INTERVAL {
-
            if self.out_of_sync {
-
                self.announce_inventory().unwrap();
-
            }
-
            self.context.io.push_back(Io::Wakeup(ANNOUNCE_INTERVAL));
-
            self.last_announce = now;
-
        }
-
        if now - self.last_prune >= PRUNE_INTERVAL {
-
            debug!("Running 'prune' task...");
-

-
            self.prune_routing_entries();
-
            self.context.io.push_back(Io::Wakeup(PRUNE_INTERVAL));
-
            self.last_prune = now;
-
        }
-
    }
-

-
    pub fn command(&mut self, cmd: Command) {
-
        debug!("Command {:?}", cmd);
-

-
        match cmd {
-
            Command::Connect(addr) => self.context.connect(addr),
-
            Command::Fetch(id, resp) => {
-
                if !self.config.is_tracking(&id) {
-
                    resp.send(FetchLookup::NotTracking).ok();
-
                    return;
-
                }
-

-
                let seeds = self.seeds(&id).collect::<Vec<_>>();
-
                let seeds = if let Some(seeds) = NonEmpty::from_vec(seeds) {
-
                    seeds
-
                } else {
-
                    log::error!("No seeds found for {}", id);
-
                    resp.send(FetchLookup::NotFound).ok();
-

-
                    return;
-
                };
-
                log::debug!("Found {} seeds for {}", seeds.len(), id);
-

-
                let mut repo = match self.storage.repository(&id) {
-
                    Ok(repo) => repo,
-
                    Err(err) => {
-
                        log::error!("Error opening repo for {}: {}", id, err);
-
                        resp.send(FetchLookup::Error(err.into())).ok();
-

-
                        return;
-
                    }
-
                };
-

-
                let (results_, results) = chan::bounded(seeds.len());
-
                resp.send(FetchLookup::Found {
-
                    seeds: seeds.clone().map(|(_, peer)| peer.addr),
-
                    results,
-
                })
-
                .ok();
-

-
                // TODO: Limit the number of seeds we fetch from? Randomize?
-
                for (_, peer) in seeds {
-
                    match repo.fetch(&Url {
-
                        scheme: git_url::Scheme::Git,
-
                        host: Some(peer.addr.ip().to_string()),
-
                        port: Some(peer.addr.port()),
-
                        // TODO: Fix upstream crate so that it adds a `/` when needed.
-
                        path: format!("/{}", id).into(),
-
                        ..Url::default()
-
                    }) {
-
                        Ok(updated) => {
-
                            results_
-
                                .send(FetchResult::Fetched {
-
                                    from: peer.addr,
-
                                    updated,
-
                                })
-
                                .ok();
-
                        }
-
                        Err(err) => {
-
                            results_
-
                                .send(FetchResult::Error {
-
                                    from: peer.addr,
-
                                    error: err.into(),
-
                                })
-
                                .ok();
-
                        }
-
                    }
-
                }
-
            }
-
            Command::Track(id, resp) => {
-
                resp.send(self.track(id)).ok();
-
            }
-
            Command::Untrack(id, resp) => {
-
                resp.send(self.untrack(id)).ok();
-
            }
-
            Command::AnnounceRefs(id) => {
-
                let node = self.node_id();
-
                let repo = self.storage.repository(&id).unwrap();
-
                let remote = repo.remote(&node).unwrap();
-
                let peers = self.peers.negotiated().map(|(_, p)| p);
-
                let refs = remote.refs.into();
-
                let message = RefsAnnouncement { id, refs };
-
                let signature = message.sign(&self.signer);
-

-
                self.context.broadcast(
-
                    Message::RefsAnnouncement {
-
                        node,
-
                        message,
-
                        signature,
-
                    },
-
                    peers,
-
                );
-
            }
-
        }
-
    }
-

-
    pub fn attempted(&mut self, addr: &std::net::SocketAddr) {
-
        let ip = addr.ip();
-
        let persistent = self.context.config.is_persistent(addr);
-
        let peer = self
-
            .peers
-
            .entry(ip)
-
            .or_insert_with(|| Peer::new(*addr, Link::Outbound, persistent));
-

-
        peer.attempted();
-
    }
-

-
    pub fn connected(
-
        &mut self,
-
        addr: std::net::SocketAddr,
-
        _local_addr: &std::net::SocketAddr,
-
        link: Link,
-
    ) {
-
        let ip = addr.ip();
-

-
        debug!("Connected to {} ({:?})", ip, link);
-

-
        // For outbound connections, we are the first to say "Hello".
-
        // For inbound connections, we wait for the remote to say "Hello" first.
-
        // TODO: How should we deal with multiple peers connecting from the same IP address?
-
        if link.is_outbound() {
-
            // TODO: Refactor this so that we don't create messages if the peer isn't found.
-
            let messages = self.handshake_messages();
-

-
            if let Some(peer) = self.peers.get_mut(&ip) {
-
                self.context.write_all(peer.addr, messages);
-
                peer.connected();
-
            }
-
        } else {
-
            self.peers.insert(
-
                ip,
-
                Peer::new(
-
                    addr,
-
                    Link::Inbound,
-
                    self.context.config.is_persistent(&addr),
-
                ),
-
            );
-
        }
-
    }
-

-
    pub fn disconnected(
-
        &mut self,
-
        addr: &std::net::SocketAddr,
-
        reason: nakamoto::DisconnectReason<DisconnectReason>,
-
    ) {
-
        let since = self.local_time();
-
        let ip = addr.ip();
-

-
        debug!("Disconnected from {} ({})", ip, reason);
-

-
        if let Some(peer) = self.peers.get_mut(&ip) {
-
            peer.state = PeerState::Disconnected { since };
-

-
            // Attempt to re-connect to persistent peers.
-
            if self.context.config.is_persistent(addr) && peer.attempts() < MAX_CONNECTION_ATTEMPTS
-
            {
-
                if reason.is_dial_err() {
-
                    return;
-
                }
-
                if let nakamoto::DisconnectReason::Protocol(r) = reason {
-
                    if !r.is_transient() {
-
                        return;
-
                    }
-
                }
-
                // TODO: Eventually we want a delay before attempting a reconnection,
-
                // with exponential back-off.
-
                debug!("Reconnecting to {} (attempts={})...", ip, peer.attempts());
-

-
                // TODO: Try to reconnect only if the peer was attempted. A disconnect without
-
                // even a successful attempt means that we're unlikely to be able to reconnect.
-

-
                self.context.connect(*addr);
-
            } else {
-
                // TODO: Non-persistent peers should be removed from the
-
                // map here or at some later point.
-
            }
-
        }
-
    }
-

-
    pub fn received_message(&mut self, addr: &std::net::SocketAddr, msg: Envelope) {
-
        let peer_ip = addr.ip();
-
        let peer = if let Some(peer) = self.peers.get_mut(&peer_ip) {
-
            peer
-
        } else {
-
            return;
-
        };
-

-
        let relay = match peer.received(msg, &mut self.context) {
-
            Ok(msg) => msg,
-
            Err(err) => {
-
                self.context
-
                    .disconnect(peer.addr, DisconnectReason::Error(err));
-
                // If there's an error, stop processing messages from this peer.
-
                // However, we still relay messages returned up to this point.
-
                //
-
                // FIXME: The peer should be set in a state such that we don'that
-
                // process further messages.
-
                return;
-
            }
-
        };
-

-
        if let Some(msg) = relay {
-
            let negotiated = self
-
                .peers
-
                .negotiated()
-
                .filter(|(ip, _)| **ip != peer_ip)
-
                .map(|(_, p)| p);
-

-
            self.context.relay(msg, negotiated.clone());
-
        }
-
    }
-

-
    ////////////////////////////////////////////////////////////////////////////
-
    // Periodic tasks
-
    ////////////////////////////////////////////////////////////////////////////
-

-
    /// Announce our inventory to all connected peers.
-
    fn announce_inventory(&mut self) -> Result<(), storage::Error> {
-
        let inv = Message::inventory(self.context.inventory_announcement()?, &self.context.signer);
-

-
        for addr in self.peers.negotiated().map(|(_, p)| p.addr) {
-
            self.context.write(addr, inv.clone());
-
        }
-
        Ok(())
-
    }
-

-
    fn prune_routing_entries(&mut self) {
-
        // TODO
-
    }
-

-
    fn maintain_connections(&mut self) {
-
        // TODO: Connect to all potential seeds.
-
        if self.peers.len() < TARGET_OUTBOUND_PEERS {
-
            let delta = TARGET_OUTBOUND_PEERS - self.peers.len();
-

-
            for _ in 0..delta {
-
                // TODO: Connect to random peer.
-
            }
-
        }
-
    }
-
}
-

-
impl<S, T, G> Deref for Protocol<S, T, G> {
-
    type Target = Context<S, T, G>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.context
-
    }
-
}
-

-
impl<S, T, G> DerefMut for Protocol<S, T, G> {
-
    fn deref_mut(&mut self) -> &mut Self::Target {
-
        &mut self.context
-
    }
-
}
-

-
#[derive(Debug, Clone)]
-
pub enum DisconnectReason {
-
    User,
-
    Error(PeerError),
-
}
-

-
impl DisconnectReason {
-
    fn is_transient(&self) -> bool {
-
        match self {
-
            Self::User => false,
-
            Self::Error(..) => false,
-
        }
-
    }
-
}
-

-
impl From<DisconnectReason> for nakamoto_net::DisconnectReason<DisconnectReason> {
-
    fn from(reason: DisconnectReason) -> Self {
-
        nakamoto_net::DisconnectReason::Protocol(reason)
-
    }
-
}
-

-
impl fmt::Display for DisconnectReason {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        match self {
-
            Self::User => write!(f, "user"),
-
            Self::Error(err) => write!(f, "error: {}", err),
-
        }
-
    }
-
}
-

-
impl<S, T, G> Iterator for Protocol<S, T, G> {
-
    type Item = Io;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        self.context.io.pop_front()
-
    }
-
}
-

-
/// Result of a project lookup.
-
#[derive(Debug)]
-
pub struct Lookup {
-
    /// Whether the project was found locally or not.
-
    pub local: Option<Project>,
-
    /// A list of remote peers on which the project is known to exist.
-
    pub remote: Vec<NodeId>,
-
}
-

-
/// Global protocol state used across peers.
-
#[derive(Debug)]
-
pub struct Context<S, T, G> {
-
    /// Protocol configuration.
-
    config: Config,
-
    /// Our cryptographic signer and key.
-
    signer: G,
-
    /// Tracks the location of projects.
-
    routing: Routing,
-
    /// Outgoing I/O queue.
-
    io: VecDeque<Io>,
-
    /// Clock. Tells the time.
-
    clock: RefClock,
-
    /// Project storage.
-
    storage: T,
-
    /// Peer address manager.
-
    addrmgr: AddressManager<S>,
-
    /// Source of entropy.
-
    rng: Rng,
-
}
-

-
impl<S, T, G> Context<S, T, G>
-
where
-
    T: storage::ReadStorage,
-
    G: crypto::Signer,
-
{
-
    pub(crate) fn node_id(&self) -> NodeId {
-
        *self.signer.public_key()
-
    }
-
}
-

-
impl<'r, S, T, G> Context<S, T, G>
-
where
-
    T: storage::WriteStorage<'r>,
-
    G: crypto::Signer,
-
{
-
    pub(crate) fn new(
-
        config: Config,
-
        clock: RefClock,
-
        storage: T,
-
        addrmgr: AddressManager<S>,
-
        signer: G,
-
        rng: Rng,
-
    ) -> Self {
-
        Self {
-
            config,
-
            signer,
-
            clock,
-
            routing: HashMap::with_hasher(rng.clone().into()),
-
            io: VecDeque::new(),
-
            storage,
-
            addrmgr,
-
            rng,
-
        }
-
    }
-

-
    fn node_announcement(&self) -> NodeAnnouncement {
-
        let timestamp = self.timestamp();
-
        let features = NodeFeatures::default();
-
        let alias = self.alias();
-
        let addresses = vec![]; // TODO
-

-
        NodeAnnouncement {
-
            features,
-
            timestamp,
-
            alias,
-
            addresses,
-
        }
-
    }
-

-
    fn inventory_announcement(&self) -> Result<InventoryAnnouncement, storage::Error> {
-
        let timestamp = self.timestamp();
-
        let inventory = self.storage.inventory()?;
-

-
        Ok(InventoryAnnouncement {
-
            inventory,
-
            timestamp,
-
        })
-
    }
-

-
    fn filter(&self) -> Filter {
-
        match &self.config.project_tracking {
-
            ProjectTracking::All { .. } => Filter::default(),
-
            ProjectTracking::Allowed(ids) => Filter::new(ids.iter()),
-
        }
-
    }
-

-
    fn handshake_messages(&self) -> [Message; 4] {
-
        let git = self.config.git_url.clone();
-
        [
-
            Message::init(
-
                self.node_id(),
-
                self.timestamp(),
-
                self.config.listen.clone(),
-
                git,
-
            ),
-
            Message::node(self.node_announcement(), &self.signer),
-
            Message::inventory(self.inventory_announcement().unwrap(), &self.signer),
-
            Message::subscribe(self.filter(), self.timestamp(), Timestamp::MAX),
-
        ]
-
    }
-

-
    fn alias(&self) -> [u8; 32] {
-
        let mut alias = [0u8; 32];
-

-
        alias[..9].copy_from_slice("anonymous".as_bytes());
-
        alias
-
    }
-

-
    /// 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 {
-
            let inventory = self
-
                .routing
-
                .entry(proj_id.clone())
-
                .or_insert_with(|| HashSet::with_hasher(self.rng.clone().into()));
-

-
            // TODO: Fire an event on routing update.
-
            if inventory.insert(from) && self.config.is_tracking(proj_id) {
-
                self.fetch(proj_id, remote);
-
            }
-
        }
-
    }
-

-
    fn fetch(&mut self, proj_id: &Id, remote: &Url) -> Vec<RefUpdate> {
-
        let mut repo = self.storage.repository(proj_id).unwrap();
-
        let mut path = remote.path.clone();
-

-
        path.push(b'/');
-
        path.extend(proj_id.to_string().into_bytes());
-

-
        repo.fetch(&Url {
-
            path,
-
            ..remote.clone()
-
        })
-
        .unwrap()
-
    }
-

-
    /// Disconnect a peer.
-
    fn disconnect(&mut self, addr: net::SocketAddr, reason: DisconnectReason) {
-
        self.io.push_back(Io::Disconnect(addr, reason));
-
    }
-
}
-

-
impl<S, T, G> Context<S, T, G> {
-
    /// Get current local timestamp.
-
    pub(crate) fn timestamp(&self) -> Timestamp {
-
        self.clock.local_time().as_secs()
-
    }
-

-
    /// Connect to a peer.
-
    fn connect(&mut self, addr: net::SocketAddr) {
-
        // TODO: Make sure we don't try to connect more than once to the same address.
-
        self.io.push_back(Io::Connect(addr));
-
    }
-

-
    fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) {
-
        let envelopes = msgs
-
            .into_iter()
-
            .map(|msg| self.config.network.envelope(msg))
-
            .collect();
-
        self.io.push_back(Io::Write(remote, envelopes));
-
    }
-

-
    fn write(&mut self, remote: net::SocketAddr, msg: Message) {
-
        debug!("Write {:?} to {}", &msg, remote.ip());
-

-
        let envelope = self.config.network.envelope(msg);
-
        self.io.push_back(Io::Write(remote, vec![envelope]));
-
    }
-

-
    /// Broadcast a message to a list of peers.
-
    fn broadcast<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Peer>) {
-
        for peer in peers {
-
            self.write(peer.addr, msg.clone());
-
        }
-
    }
-

-
    /// Relay a message to interested peers.
-
    fn relay<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Peer>) {
-
        if let Message::RefsAnnouncement { message, .. } = &msg {
-
            let id = message.id.clone();
-
            let peers = peers.into_iter().filter(|p| {
-
                if let Some(subscribe) = &p.subscribe {
-
                    subscribe.filter.contains(&id)
-
                } else {
-
                    // If the peer did not send us a `subscribe` message, we don'the
-
                    // relay any messages to them.
-
                    false
-
                }
-
            });
-
            self.broadcast(msg, peers);
-
        } else {
-
            self.broadcast(msg, peers);
-
        }
-
    }
-
}
-

-
#[derive(Debug)]
-
/// Holds currently (or recently) connected peers.
-
pub struct Peers(AddressBook<IpAddr, Peer>);
-

-
impl Peers {
-
    pub fn new(rng: Rng) -> Self {
-
        Self(AddressBook::new(rng))
-
    }
-

-
    pub fn by_id(&self, id: &NodeId) -> Option<&Peer> {
-
        self.0.values().find(|p| {
-
            if let PeerState::Negotiated { id: _id, .. } = &p.state {
-
                _id == id
-
            } else {
-
                false
-
            }
-
        })
-
    }
-

-
    /// Iterator over fully negotiated peers.
-
    pub fn negotiated(&self) -> impl Iterator<Item = (&IpAddr, &Peer)> + Clone {
-
        self.0.iter().filter(move |(_, p)| p.is_negotiated())
-
    }
-
}
-

-
impl Deref for Peers {
-
    type Target = AddressBook<IpAddr, Peer>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl DerefMut for Peers {
-
    fn deref_mut(&mut self) -> &mut Self::Target {
-
        &mut self.0
-
    }
-
}
deleted node/src/protocol/config.rs
@@ -1,128 +0,0 @@
-
use std::net;
-

-
use git_url::Url;
-

-
use crate::collections::HashSet;
-
use crate::git;
-
use crate::identity::{Id, PublicKey};
-
use crate::protocol::message::{Address, Envelope, Message};
-

-
/// Peer-to-peer network.
-
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
-
pub enum Network {
-
    #[default]
-
    Main,
-
    Test,
-
}
-

-
impl Network {
-
    pub fn magic(&self) -> u32 {
-
        match self {
-
            Self::Main => 0x819b43d9,
-
            Self::Test => 0x717ebaf8,
-
        }
-
    }
-

-
    pub fn envelope(&self, msg: Message) -> Envelope {
-
        Envelope {
-
            magic: self.magic(),
-
            msg,
-
        }
-
    }
-
}
-

-
/// Project tracking policy.
-
#[derive(Debug, Clone)]
-
pub enum ProjectTracking {
-
    /// Track all projects we come across.
-
    All { blocked: HashSet<Id> },
-
    /// Track a static list of projects.
-
    Allowed(HashSet<Id>),
-
}
-

-
impl Default for ProjectTracking {
-
    fn default() -> Self {
-
        Self::All {
-
            blocked: HashSet::default(),
-
        }
-
    }
-
}
-

-
/// Project remote tracking policy.
-
#[derive(Debug, Default, Clone)]
-
pub enum RemoteTracking {
-
    /// Only track remotes of project delegates.
-
    #[default]
-
    DelegatesOnly,
-
    /// Track all remotes.
-
    All { blocked: HashSet<PublicKey> },
-
    /// Track a specific list of users as well as the project delegates.
-
    Allowed(HashSet<PublicKey>),
-
}
-

-
/// Protocol configuration.
-
#[derive(Debug, Clone)]
-
pub struct Config {
-
    /// Peers to connect to on startup.
-
    /// Connections to these peers will be maintained.
-
    pub connect: Vec<net::SocketAddr>,
-
    /// Peer-to-peer network.
-
    pub network: Network,
-
    /// Project tracking policy.
-
    pub project_tracking: ProjectTracking,
-
    /// Project remote tracking policy.
-
    pub remote_tracking: RemoteTracking,
-
    /// Whether or not our node should relay inventories.
-
    pub relay: bool,
-
    /// List of addresses to listen on for protocol connections.
-
    pub listen: Vec<Address>,
-
    /// Our Git URL for fetching projects.
-
    pub git_url: Url,
-
}
-

-
impl Default for Config {
-
    fn default() -> Self {
-
        Self {
-
            connect: Vec::default(),
-
            network: Network::default(),
-
            project_tracking: ProjectTracking::default(),
-
            remote_tracking: RemoteTracking::default(),
-
            relay: true,
-
            listen: vec![],
-
            git_url: Url {
-
                scheme: git::url::Scheme::File,
-
                path: "/dev/null".to_owned().into(),
-
                ..Url::default()
-
            },
-
        }
-
    }
-
}
-

-
impl Config {
-
    pub fn is_persistent(&self, addr: &net::SocketAddr) -> bool {
-
        self.connect.contains(addr)
-
    }
-

-
    pub fn is_tracking(&self, id: &Id) -> bool {
-
        match &self.project_tracking {
-
            ProjectTracking::All { blocked } => !blocked.contains(id),
-
            ProjectTracking::Allowed(ids) => ids.contains(id),
-
        }
-
    }
-

-
    /// Track a project. Returns whether the policy was updated.
-
    pub fn track(&mut self, id: Id) -> bool {
-
        match &mut self.project_tracking {
-
            ProjectTracking::All { .. } => false,
-
            ProjectTracking::Allowed(ids) => ids.insert(id),
-
        }
-
    }
-

-
    /// Untrack a project. Returns whether the policy was updated.
-
    pub fn untrack(&mut self, id: Id) -> bool {
-
        match &mut self.project_tracking {
-
            ProjectTracking::All { blocked } => blocked.insert(id),
-
            ProjectTracking::Allowed(ids) => ids.remove(&id),
-
        }
-
    }
-
}
deleted node/src/protocol/filter.rs
@@ -1,84 +0,0 @@
-
use std::io;
-
use std::ops::{Deref, DerefMut};
-

-
use bloomy::BloomFilter;
-

-
use crate::identity::Id;
-
use crate::protocol::wire;
-

-
/// Size in bytes of subscription bloom filter.
-
pub const FILTER_SIZE: usize = 1024 * 16;
-
/// Number of hashes used for bloom filter.
-
pub const FILTER_HASHES: usize = 7;
-

-
/// Subscription filter.
-
///
-
/// The [`Default`] instance has all bits set to `1`, ie. it will match
-
/// everything.
-
///
-
/// Nb. This filter doesn't currently support inserting public keys.
-
#[derive(Clone, PartialEq, Eq, Debug)]
-
pub struct Filter(BloomFilter<Id>);
-

-
impl Default for Filter {
-
    fn default() -> Self {
-
        Self(BloomFilter::from(vec![0xff; FILTER_SIZE]))
-
    }
-
}
-

-
impl Filter {
-
    pub fn new<'a>(ids: impl IntoIterator<Item = &'a Id>) -> Self {
-
        let mut bloom = BloomFilter::with_size(FILTER_SIZE);
-

-
        for id in ids.into_iter() {
-
            bloom.insert(id);
-
        }
-
        Self(bloom)
-
    }
-
}
-

-
impl Deref for Filter {
-
    type Target = BloomFilter<Id>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl DerefMut for Filter {
-
    fn deref_mut(&mut self) -> &mut Self::Target {
-
        &mut self.0
-
    }
-
}
-

-
#[cfg(test)]
-
impl From<BloomFilter<Id>> for Filter {
-
    fn from(bloom: BloomFilter<Id>) -> Self {
-
        Self(bloom)
-
    }
-
}
-

-
impl wire::Encode for Filter {
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        let mut n = 0;
-

-
        n += self.0.as_bytes().encode(writer)?;
-

-
        Ok(n)
-
    }
-
}
-

-
impl wire::Decode for Filter {
-
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let size: wire::Size = wire::Decode::decode(reader)?;
-
        if size as usize != FILTER_SIZE {
-
            return Err(wire::Error::InvalidFilterSize(size as usize));
-
        }
-
        let bytes: [u8; FILTER_SIZE] = wire::Decode::decode(reader)?;
-
        let bf = BloomFilter::from(Vec::from(bytes));
-

-
        debug_assert_eq!(bf.hashes(), FILTER_HASHES);
-

-
        Ok(Self(bf))
-
    }
-
}
deleted node/src/protocol/message.rs
@@ -1,663 +0,0 @@
-
use std::{fmt, io, net};
-

-
use byteorder::{NetworkEndian, ReadBytesExt};
-

-
use crate::crypto;
-
use crate::git;
-
use crate::identity::Id;
-
use crate::protocol::filter::Filter;
-
use crate::protocol::wire;
-
use crate::protocol::{NodeId, Timestamp, PROTOCOL_VERSION};
-
use crate::storage::refs::Refs;
-

-
/// 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);
-

-
/// Message type.
-
#[repr(u16)]
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-
pub enum MessageType {
-
    Initialize = 0,
-
    NodeAnnouncement = 2,
-
    InventoryAnnouncement = 4,
-
    RefsAnnouncement = 6,
-
    Subscribe = 8,
-
}
-

-
impl From<MessageType> for u16 {
-
    fn from(other: MessageType) -> Self {
-
        other as u16
-
    }
-
}
-

-
impl TryFrom<u16> for MessageType {
-
    type Error = u16;
-

-
    fn try_from(other: u16) -> Result<Self, Self::Error> {
-
        match other {
-
            0 => Ok(MessageType::Initialize),
-
            2 => Ok(MessageType::NodeAnnouncement),
-
            4 => Ok(MessageType::InventoryAnnouncement),
-
            6 => Ok(MessageType::RefsAnnouncement),
-
            8 => Ok(MessageType::Subscribe),
-
            _ => Err(other),
-
        }
-
    }
-
}
-

-
/// Address type.
-
#[repr(u8)]
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-
pub enum AddressType {
-
    Ipv4 = 1,
-
    Ipv6 = 2,
-
    Hostname = 3,
-
    Onion = 4,
-
}
-

-
impl From<AddressType> for u8 {
-
    fn from(other: AddressType) -> Self {
-
        other as u8
-
    }
-
}
-

-
impl TryFrom<u8> for AddressType {
-
    type Error = u8;
-

-
    fn try_from(other: u8) -> Result<Self, Self::Error> {
-
        match other {
-
            1 => Ok(AddressType::Ipv4),
-
            2 => Ok(AddressType::Ipv6),
-
            3 => Ok(AddressType::Hostname),
-
            4 => Ok(AddressType::Onion),
-
            _ => Err(other),
-
        }
-
    }
-
}
-

-
/// 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<net::SocketAddr> 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 },
-
        }
-
    }
-
}
-

-
impl wire::Encode for Envelope {
-
    fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
-
        let mut n = 0;
-

-
        n += self.magic.encode(writer)?;
-
        n += self.msg.encode(writer)?;
-

-
        Ok(n)
-
    }
-
}
-

-
impl wire::Decode for Envelope {
-
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let magic = u32::decode(reader)?;
-
        let msg = Message::decode(reader)?;
-

-
        Ok(Self { magic, msg })
-
    }
-
}
-

-
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::Ipv4 { ip, port } => {
-
                n += u8::from(AddressType::Ipv4).encode(writer)?;
-
                n += ip.octets().encode(writer)?;
-
                n += port.encode(writer)?;
-
            }
-
            Self::Ipv6 { ip, port } => {
-
                n += u8::from(AddressType::Ipv6).encode(writer)?;
-
                n += ip.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> {
-
        let addrtype = reader.read_u8()?;
-

-
        match AddressType::try_from(addrtype) {
-
            Ok(AddressType::Ipv4) => {
-
                let octets: [u8; 4] = wire::Decode::decode(reader)?;
-
                let ip = net::Ipv4Addr::from(octets);
-
                let port = u16::decode(reader)?;
-

-
                Ok(Self::Ipv4 { ip, port })
-
            }
-
            Ok(AddressType::Ipv6) => {
-
                let octets: [u8; 16] = wire::Decode::decode(reader)?;
-
                let ip = net::Ipv6Addr::from(octets);
-
                let port = u16::decode(reader)?;
-

-
                Ok(Self::Ipv6 { ip, port })
-
            }
-
            Ok(AddressType::Hostname) => {
-
                todo!();
-
            }
-
            Ok(AddressType::Onion) => {
-
                todo!();
-
            }
-
            Err(other) => Err(wire::Error::UnknownAddressType(other)),
-
        }
-
    }
-
}
-

-
#[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<Address>,
-
}
-

-
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<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        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<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let features = NodeFeatures::decode(reader)?;
-
        let timestamp = Timestamp::decode(reader)?;
-
        let alias = wire::Decode::decode(reader)?;
-
        let addresses = Vec::<Address>::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<S: crypto::Signer>(&self, signer: S) -> crypto::Signature {
-
        let msg = wire::serialize(self);
-
        signer.sign(&msg)
-
    }
-
}
-

-
impl wire::Encode for RefsAnnouncement {
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        let mut n = 0;
-

-
        n += self.id.encode(writer)?;
-
        n += self.refs.encode(writer)?;
-

-
        Ok(n)
-
    }
-
}
-

-
impl wire::Decode for RefsAnnouncement {
-
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let id = Id::decode(reader)?;
-
        let refs = Refs::decode(reader)?;
-

-
        Ok(Self { id, refs })
-
    }
-
}
-

-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct InventoryAnnouncement {
-
    pub inventory: Vec<Id>,
-
    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()
-
    }
-
}
-

-
impl wire::Encode for InventoryAnnouncement {
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        let mut n = 0;
-

-
        n += self.inventory.as_slice().encode(writer)?;
-
        n += self.timestamp.encode(writer)?;
-

-
        Ok(n)
-
    }
-
}
-

-
impl wire::Decode for InventoryAnnouncement {
-
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let inventory = Vec::<Id>::decode(reader)?;
-
        let timestamp = Timestamp::decode(reader)?;
-

-
        Ok(Self {
-
            inventory,
-
            timestamp,
-
        })
-
    }
-
}
-

-
/// 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<Address>,
-
        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<Address>, git: git::Url) -> Self {
-
        Self::Initialize {
-
            id,
-
            timestamp,
-
            version: PROTOCOL_VERSION,
-
            addrs,
-
            git,
-
        }
-
    }
-

-
    pub fn node<S: crypto::Signer>(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<S: crypto::Signer>(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,
-
        })
-
    }
-

-
    pub fn type_id(&self) -> u16 {
-
        match self {
-
            Self::Initialize { .. } => MessageType::Initialize,
-
            Self::Subscribe { .. } => MessageType::Subscribe,
-
            Self::NodeAnnouncement { .. } => MessageType::NodeAnnouncement,
-
            Self::InventoryAnnouncement { .. } => MessageType::InventoryAnnouncement,
-
            Self::RefsAnnouncement { .. } => MessageType::RefsAnnouncement,
-
        }
-
        .into()
-
    }
-
}
-

-
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::<Vec<String>>()
-
                        .join(", "),
-
                    message.timestamp
-
                )
-
            }
-
            Self::RefsAnnouncement { node, message, .. } => {
-
                write!(
-
                    f,
-
                    "RefsAnnouncement({}, {}, {:?})",
-
                    node, message.id, message.refs
-
                )
-
            }
-
        }
-
    }
-
}
-

-
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::Initialize {
-
                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::Subscribe(Subscribe {
-
                filter,
-
                since,
-
                until,
-
            }) => {
-
                n += filter.encode(writer)?;
-
                n += since.encode(writer)?;
-
                n += until.encode(writer)?;
-
            }
-
            Self::RefsAnnouncement {
-
                node,
-
                message,
-
                signature,
-
            } => {
-
                n += node.encode(writer)?;
-
                n += message.encode(writer)?;
-
                n += signature.encode(writer)?;
-
            }
-
            Self::InventoryAnnouncement {
-
                node,
-
                message,
-
                signature,
-
            } => {
-
                n += node.encode(writer)?;
-
                n += message.encode(writer)?;
-
                n += signature.encode(writer)?;
-
            }
-
            Self::NodeAnnouncement {
-
                node,
-
                message,
-
                signature,
-
            } => {
-
                n += node.encode(writer)?;
-
                n += message.encode(writer)?;
-
                n += signature.encode(writer)?;
-
            }
-
        }
-
        Ok(n)
-
    }
-
}
-

-
impl wire::Decode for Message {
-
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
-
        let type_id = reader.read_u16::<NetworkEndian>()?;
-

-
        match MessageType::try_from(type_id) {
-
            Ok(MessageType::Initialize) => {
-
                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::Initialize {
-
                    id,
-
                    timestamp,
-
                    version,
-
                    addrs,
-
                    git,
-
                })
-
            }
-
            Ok(MessageType::Subscribe) => {
-
                let filter = Filter::decode(reader)?;
-
                let since = Timestamp::decode(reader)?;
-
                let until = Timestamp::decode(reader)?;
-

-
                Ok(Self::Subscribe(Subscribe {
-
                    filter,
-
                    since,
-
                    until,
-
                }))
-
            }
-
            Ok(MessageType::NodeAnnouncement) => {
-
                let node = NodeId::decode(reader)?;
-
                let message = NodeAnnouncement::decode(reader)?;
-
                let signature = crypto::Signature::decode(reader)?;
-

-
                Ok(Self::NodeAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                })
-
            }
-
            Ok(MessageType::InventoryAnnouncement) => {
-
                let node = NodeId::decode(reader)?;
-
                let message = InventoryAnnouncement::decode(reader)?;
-
                let signature = crypto::Signature::decode(reader)?;
-

-
                Ok(Self::InventoryAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                })
-
            }
-
            Ok(MessageType::RefsAnnouncement) => {
-
                let node = NodeId::decode(reader)?;
-
                let message = RefsAnnouncement::decode(reader)?;
-
                let signature = crypto::Signature::decode(reader)?;
-

-
                Ok(Self::RefsAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                })
-
            }
-
            Err(other) => Err(wire::Error::UnknownMessageType(other)),
-
        }
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use super::*;
-
    use quickcheck_macros::quickcheck;
-

-
    use crate::crypto::Signer;
-
    use crate::decoder::Decoder;
-
    use crate::protocol::wire::{self, Encode};
-
    use crate::test::crypto::MockSigner;
-

-
    #[quickcheck]
-
    fn prop_message_encode_decode(message: Message) {
-
        assert_eq!(
-
            wire::deserialize::<Message>(&wire::serialize(&message)).unwrap(),
-
            message
-
        );
-
    }
-

-
    #[quickcheck]
-
    fn prop_envelope_encode_decode(envelope: Envelope) {
-
        assert_eq!(
-
            wire::deserialize::<Envelope>(&wire::serialize(&envelope)).unwrap(),
-
            envelope
-
        );
-
    }
-

-
    #[test]
-
    fn prop_envelope_decoder() {
-
        fn property(items: Vec<Envelope>) {
-
            let mut decoder = Decoder::<Envelope>::new(8);
-

-
            for item in &items {
-
                item.encode(&mut decoder).unwrap();
-
            }
-
            for item in items {
-
                assert_eq!(decoder.next().unwrap().unwrap(), item);
-
            }
-
        }
-

-
        quickcheck::QuickCheck::new()
-
            .gen(quickcheck::Gen::new(16))
-
            .quickcheck(property as fn(items: Vec<Envelope>));
-
    }
-

-
    #[quickcheck]
-
    fn prop_addr(addr: Address) {
-
        assert_eq!(
-
            wire::deserialize::<Address>(&wire::serialize(&addr)).unwrap(),
-
            addr
-
        );
-
    }
-

-
    #[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));
-
    }
-
}
deleted node/src/protocol/peer.rs
@@ -1,244 +0,0 @@
-
use crate::protocol::message::*;
-
use crate::protocol::*;
-

-
#[derive(Debug, Default)]
-
#[allow(clippy::large_enum_variant)]
-
pub enum PeerState {
-
    /// Initial peer state. For outgoing peers this
-
    /// means we've attempted a connection. For incoming
-
    /// peers, this means they've successfully connected
-
    /// to us.
-
    #[default]
-
    Initial,
-
    /// State after successful handshake.
-
    Negotiated {
-
        /// The peer's unique identifier.
-
        id: NodeId,
-
        since: LocalTime,
-
        /// Addresses this peer is reachable on.
-
        addrs: Vec<Address>,
-
        git: Url,
-
    },
-
    /// When a peer is disconnected.
-
    Disconnected { since: LocalTime },
-
}
-

-
#[derive(thiserror::Error, Debug, Clone)]
-
pub enum PeerError {
-
    #[error("wrong network constant in message: {0}")]
-
    WrongMagic(u32),
-
    #[error("wrong protocol version in message: {0}")]
-
    WrongVersion(u32),
-
    #[error("invalid inventory timestamp: {0}")]
-
    InvalidTimestamp(u64),
-
    #[error("peer misbehaved")]
-
    Misbehavior,
-
}
-

-
#[derive(Debug)]
-
pub struct Peer {
-
    /// Peer address.
-
    pub addr: net::SocketAddr,
-
    /// Connection direction.
-
    pub link: Link,
-
    /// Whether we should attempt to re-connect
-
    /// to this peer upon disconnection.
-
    pub persistent: bool,
-
    /// Peer connection state.
-
    pub state: PeerState,
-
    /// Last known peer time.
-
    pub timestamp: Timestamp,
-
    /// Peer subscription.
-
    pub subscribe: Option<Subscribe>,
-

-
    /// Connection attempts. For persistent peers, Tracks
-
    /// how many times we've attempted to connect. We reset this to zero
-
    /// upon successful connection.
-
    attempts: usize,
-
}
-

-
impl Peer {
-
    pub fn new(addr: net::SocketAddr, link: Link, persistent: bool) -> Self {
-
        Self {
-
            addr,
-
            state: PeerState::default(),
-
            link,
-
            timestamp: Timestamp::default(),
-
            subscribe: None,
-
            persistent,
-
            attempts: 0,
-
        }
-
    }
-

-
    pub fn ip(&self) -> IpAddr {
-
        self.addr.ip()
-
    }
-

-
    pub fn is_negotiated(&self) -> bool {
-
        matches!(self.state, PeerState::Negotiated { .. })
-
    }
-

-
    pub fn attempts(&self) -> usize {
-
        self.attempts
-
    }
-

-
    pub fn attempted(&mut self) {
-
        self.attempts += 1;
-
    }
-

-
    pub fn connected(&mut self) {
-
        self.attempts = 0;
-
    }
-

-
    pub fn received<'r, S, T, G>(
-
        &mut self,
-
        envelope: Envelope,
-
        ctx: &mut Context<S, T, G>,
-
    ) -> Result<Option<Message>, PeerError>
-
    where
-
        T: storage::WriteStorage<'r>,
-
        G: crypto::Signer,
-
    {
-
        if envelope.magic != ctx.config.network.magic() {
-
            return Err(PeerError::WrongMagic(envelope.magic));
-
        }
-
        debug!("Received {:?} from {}", &envelope.msg, self.ip());
-

-
        match (&self.state, envelope.msg) {
-
            (
-
                PeerState::Initial,
-
                Message::Initialize {
-
                    id,
-
                    timestamp,
-
                    version,
-
                    addrs,
-
                    git,
-
                },
-
            ) => {
-
                let now = ctx.timestamp();
-

-
                if timestamp.abs_diff(now) > MAX_TIME_DELTA.as_secs() {
-
                    return Err(PeerError::InvalidTimestamp(timestamp));
-
                }
-
                if version != PROTOCOL_VERSION {
-
                    return Err(PeerError::WrongVersion(version));
-
                }
-
                // Nb. This is a very primitive handshake. Eventually we should have anyhow
-
                // extra "acknowledgment" message sent when the `Initialize` is well received.
-
                if self.link.is_inbound() {
-
                    ctx.write_all(self.addr, ctx.handshake_messages());
-
                }
-
                // Nb. we don't set the peer timestamp here, since it is going to be
-
                // set after the first message is received only. Setting it here would
-
                // mean that messages received right after the handshake could be ignored.
-
                self.state = PeerState::Negotiated {
-
                    id,
-
                    since: ctx.clock.local_time(),
-
                    addrs,
-
                    git,
-
                };
-
            }
-
            (PeerState::Initial, _) => {
-
                debug!(
-
                    "Disconnecting peer {} for sending us a message before handshake",
-
                    self.ip()
-
                );
-
                return Err(PeerError::Misbehavior);
-
            }
-
            (
-
                PeerState::Negotiated { git, .. },
-
                Message::InventoryAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                },
-
            ) => {
-
                let now = ctx.clock.local_time();
-
                let last = self.timestamp;
-

-
                // Don't allow messages from too far in the past or future.
-
                if message.timestamp.abs_diff(now.as_secs()) > MAX_TIME_DELTA.as_secs() {
-
                    return Err(PeerError::InvalidTimestamp(message.timestamp));
-
                }
-
                // Discard inventory messages we've already seen, otherwise update
-
                // out last seen time.
-
                if message.timestamp > last {
-
                    self.timestamp = message.timestamp;
-
                } else {
-
                    return Ok(None);
-
                }
-
                ctx.process_inventory(&message.inventory, node, git);
-

-
                if ctx.config.relay {
-
                    return Ok(Some(Message::InventoryAnnouncement {
-
                        node,
-
                        message,
-
                        signature,
-
                    }));
-
                }
-
            }
-
            // Process a peer inventory update announcement by (maybe) fetching.
-
            (
-
                PeerState::Negotiated { git, .. },
-
                Message::RefsAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                },
-
            ) => {
-
                if message.verify(&node, &signature) {
-
                    // TODO: Buffer/throttle fetches.
-
                    // TODO: Check that we're tracking this user as well.
-
                    if ctx.config.is_tracking(&message.id) {
-
                        // TODO: Check refs to see if we should try to fetch or not.
-
                        let updated_refs = ctx.fetch(&message.id, git);
-
                        let is_updated = !updated_refs.is_empty();
-

-
                        ctx.io.push_back(Io::Event(Event::RefsFetched {
-
                            from: git.clone(),
-
                            project: message.id.clone(),
-
                            updated: updated_refs,
-
                        }));
-

-
                        if is_updated {
-
                            return Ok(Some(Message::RefsAnnouncement {
-
                                node,
-
                                message,
-
                                signature,
-
                            }));
-
                        }
-
                    }
-
                } else {
-
                    return Err(PeerError::Misbehavior);
-
                }
-
            }
-
            (
-
                PeerState::Negotiated { .. },
-
                Message::NodeAnnouncement {
-
                    node,
-
                    message,
-
                    signature,
-
                },
-
            ) => {
-
                if !message.verify(&node, &signature) {
-
                    return Err(PeerError::Misbehavior);
-
                }
-
                log::warn!("Node announcement handling is not implemented");
-
            }
-
            (PeerState::Negotiated { .. }, Message::Subscribe(subscribe)) => {
-
                self.subscribe = Some(subscribe);
-
            }
-
            (PeerState::Negotiated { .. }, Message::Initialize { .. }) => {
-
                debug!(
-
                    "Disconnecting peer {} for sending us a redundant handshake message",
-
                    self.ip()
-
                );
-
                return Err(PeerError::Misbehavior);
-
            }
-
            (PeerState::Disconnected { .. }, msg) => {
-
                debug!("Ignoring {:?} from disconnected peer {}", msg, self.ip());
-
            }
-
        }
-
        Ok(None)
-
    }
-
}
deleted node/src/protocol/wire.rs
@@ -1,633 +0,0 @@
-
use std::collections::{BTreeMap, HashMap};
-
use std::convert::TryFrom;
-
use std::net::{self, IpAddr};
-
use std::ops::{Deref, DerefMut};
-
use std::string::FromUtf8Error;
-
use std::{io, mem};
-

-
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
-
use nakamoto_net as nakamoto;
-
use nakamoto_net::{Link, LocalTime};
-

-
use crate::address_book;
-
use crate::crypto::{PublicKey, Signature, Signer};
-
use crate::decoder::Decoder;
-
use crate::git;
-
use crate::git::fmt;
-
use crate::hash::Digest;
-
use crate::identity::Id;
-
use crate::protocol;
-
use crate::storage::refs::Refs;
-
use crate::storage::WriteStorage;
-

-
/// The default type we use to represent sizes.
-
/// Four bytes is more than enough for anything sent over the wire.
-
/// Note that in certain cases, we may use only one or two byte types.
-
pub type Size = u32;
-

-
#[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("invalid filter size: {0}")]
-
    InvalidFilterSize(usize),
-
    #[error(transparent)]
-
    InvalidRefName(#[from] fmt::Error),
-
    #[error("invalid git url `{url}`: {error}")]
-
    InvalidGitUrl {
-
        url: String,
-
        error: git::url::parse::Error,
-
    },
-
    #[error("unknown address type `{0}`")]
-
    UnknownAddressType(u8),
-
    #[error("unknown message type `{0}`")]
-
    UnknownMessageType(u16),
-
}
-

-
impl Error {
-
    /// Whether we've reached the end of file. This will be true when we fail to decode
-
    /// a message because there's not enough data in the stream.
-
    pub fn is_eof(&self) -> bool {
-
        matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
-
    }
-
}
-

-
/// Things that can be encoded as binary.
-
pub trait Encode {
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error>;
-
}
-

-
/// Things that can be decoded from binary.
-
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
-
}
-

-
/// Decode an object from a vector.
-
pub fn deserialize<T: Decode>(data: &[u8]) -> Result<T, Error> {
-
    let mut cursor = io::Cursor::new(data);
-

-
    T::decode(&mut cursor)
-
}
-

-
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 {
-
    /// We encode this type to a [`u32`], since there's no need to send larger messages
-
    /// over the network.
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        assert!(
-
            *self <= u32::MAX as usize,
-
            "Cannot encode sizes larger than {}",
-
            u32::MAX
-
        );
-
        writer.write_u32::<NetworkEndian>(*self as u32)?;
-

-
        Ok(mem::size_of::<u32>())
-
    }
-
}
-

-
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() as Size).encode(writer)?;
-

-
        for item in self.iter() {
-
            n += item.encode(writer)?;
-
        }
-
        Ok(n)
-
    }
-
}
-

-
impl Encode for &str {
-
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-
        assert!(self.len() <= u8::MAX as usize);
-

-
        let n = (self.len() as u8).encode(writer)?;
-
        let bytes = self.as_bytes();
-

-
        // Nb. Don't use the [`Encode`] instance here for &[u8], because we are prefixing the
-
        // length ourselves.
-
        writer.write_all(bytes)?;
-

-
        Ok(n + bytes.len())
-
    }
-
}
-

-
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 = u32::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: Size = Size::decode(reader)?;
-
        let mut vec = Vec::with_capacity(len as usize);
-

-
        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 = u8::decode(reader)?;
-
        let mut bytes = vec![0; len as usize];
-

-
        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 url = String::decode(reader)?;
-
        let url = Self::from_bytes(url.as_bytes())
-
            .map_err(|error| Error::InvalidGitUrl { url, error })?;
-

-
        Ok(url)
-
    }
-
}
-

-
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))
-
    }
-
}
-

-
#[derive(Debug)]
-
pub struct Wire<S, T, G> {
-
    inboxes: HashMap<IpAddr, Decoder>,
-
    inner: protocol::Protocol<S, T, G>,
-
}
-

-
impl<S, T, G> Wire<S, T, G> {
-
    pub fn new(inner: protocol::Protocol<S, T, G>) -> Self {
-
        Self {
-
            inboxes: HashMap::new(),
-
            inner,
-
        }
-
    }
-
}
-

-
impl<'r, S, T, G> Wire<S, T, G>
-
where
-
    S: address_book::Store,
-
    T: WriteStorage<'r> + 'static,
-
    G: Signer,
-
{
-
    pub fn initialize(&mut self, time: LocalTime) {
-
        self.inner.initialize(time)
-
    }
-

-
    pub fn tick(&mut self, now: LocalTime) {
-
        self.inner.tick(now)
-
    }
-

-
    pub fn wake(&mut self) {
-
        self.inner.wake()
-
    }
-

-
    pub fn command(&mut self, cmd: protocol::Command) {
-
        self.inner.command(cmd)
-
    }
-

-
    pub fn attempted(&mut self, addr: &net::SocketAddr) {
-
        self.inner.attempted(addr)
-
    }
-

-
    pub fn connected(
-
        &mut self,
-
        addr: std::net::SocketAddr,
-
        local_addr: &std::net::SocketAddr,
-
        link: Link,
-
    ) {
-
        self.inboxes.insert(addr.ip(), Decoder::new(256));
-
        self.inner.connected(addr, local_addr, link)
-
    }
-

-
    pub fn disconnected(
-
        &mut self,
-
        addr: &std::net::SocketAddr,
-
        reason: nakamoto::DisconnectReason<protocol::DisconnectReason>,
-
    ) {
-
        self.inboxes.remove(&addr.ip());
-
        self.inner.disconnected(addr, reason)
-
    }
-

-
    pub fn received_bytes(&mut self, addr: &std::net::SocketAddr, bytes: &[u8]) {
-
        let peer_ip = addr.ip();
-

-
        if let Some(inbox) = self.inboxes.get_mut(&peer_ip) {
-
            inbox.input(bytes);
-

-
            loop {
-
                match inbox.decode_next() {
-
                    Ok(Some(msg)) => self.inner.received_message(addr, msg),
-
                    Ok(None) => break,
-

-
                    Err(err) => {
-
                        // TODO: Disconnect peer.
-
                        log::error!("Invalid message received from {}: {}", peer_ip, err);
-

-
                        return;
-
                    }
-
                }
-
            }
-
        } else {
-
            log::debug!("Received message from unknown peer {}", peer_ip);
-
        }
-
    }
-
}
-

-
impl<S, T, G> Iterator for Wire<S, T, G> {
-
    type Item = nakamoto::Io<protocol::Event, protocol::DisconnectReason>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        match self.inner.next() {
-
            Some(protocol::Io::Write(addr, msgs)) => {
-
                let mut buf = Vec::new();
-
                for msg in msgs {
-
                    log::debug!("Write {:?} to {}", &msg, addr.ip());
-

-
                    msg.encode(&mut buf)
-
                        .expect("writing to an in-memory buffer doesn't fail");
-
                }
-
                Some(nakamoto::Io::Write(addr, buf))
-
            }
-
            Some(protocol::Io::Event(e)) => Some(nakamoto::Io::Event(e)),
-
            Some(protocol::Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
-
            Some(protocol::Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
-
            Some(protocol::Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
-

-
            None => None,
-
        }
-
    }
-
}
-

-
impl<S, T, G> Deref for Wire<S, T, G> {
-
    type Target = protocol::Protocol<S, T, G>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.inner
-
    }
-
}
-
impl<S, T, G> DerefMut for Wire<S, T, G> {
-
    fn deref_mut(&mut self) -> &mut Self::Target {
-
        &mut self.inner
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use super::*;
-
    use quickcheck_macros::quickcheck;
-

-
    use crate::crypto::Unverified;
-
    use crate::storage::refs::SignedRefs;
-
    use crate::test::arbitrary;
-

-
    #[quickcheck]
-
    fn prop_u8(input: u8) {
-
        assert_eq!(deserialize::<u8>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_u16(input: u16) {
-
        assert_eq!(deserialize::<u16>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_u32(input: u32) {
-
        assert_eq!(deserialize::<u32>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_u64(input: u64) {
-
        assert_eq!(deserialize::<u64>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_usize(input: usize) -> quickcheck::TestResult {
-
        if input > u32::MAX as usize {
-
            return quickcheck::TestResult::discard();
-
        }
-
        assert_eq!(deserialize::<usize>(&serialize(&input)).unwrap(), input);
-

-
        quickcheck::TestResult::passed()
-
    }
-

-
    #[quickcheck]
-
    fn prop_string(input: String) -> quickcheck::TestResult {
-
        if input.len() > u8::MAX as usize {
-
            return quickcheck::TestResult::discard();
-
        }
-
        assert_eq!(deserialize::<String>(&serialize(&input)).unwrap(), input);
-

-
        quickcheck::TestResult::passed()
-
    }
-

-
    #[quickcheck]
-
    fn prop_vec(input: Vec<String>) {
-
        assert_eq!(
-
            deserialize::<Vec<String>>(&serialize(&input.as_slice())).unwrap(),
-
            input
-
        );
-
    }
-

-
    #[quickcheck]
-
    fn prop_pubkey(input: PublicKey) {
-
        assert_eq!(deserialize::<PublicKey>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_id(input: Id) {
-
        assert_eq!(deserialize::<Id>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_digest(input: Digest) {
-
        assert_eq!(deserialize::<Digest>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_refs(input: Refs) {
-
        assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
-
    }
-

-
    #[quickcheck]
-
    fn prop_signature(input: arbitrary::ByteArray<64>) {
-
        let signature = Signature::from(input.into_inner());
-

-
        assert_eq!(
-
            deserialize::<Signature>(&serialize(&signature)).unwrap(),
-
            signature
-
        );
-
    }
-

-
    #[quickcheck]
-
    fn prop_oid(input: arbitrary::ByteArray<20>) {
-
        let oid = git::Oid::try_from(input.into_inner().as_slice()).unwrap();
-

-
        assert_eq!(deserialize::<git::Oid>(&serialize(&oid)).unwrap(), oid);
-
    }
-

-
    #[quickcheck]
-
    fn prop_signed_refs(input: SignedRefs<Unverified>) {
-
        assert_eq!(
-
            deserialize::<SignedRefs<Unverified>>(&serialize(&input)).unwrap(),
-
            input
-
        );
-
    }
-

-
    #[test]
-
    fn test_string() {
-
        assert_eq!(
-
            serialize(&String::from("hello")),
-
            vec![5, b'h', b'e', b'l', b'l', b'o']
-
        );
-
    }
-

-
    #[test]
-
    fn test_git_url() {
-
        let url = git::Url {
-
            scheme: git::url::Scheme::Https,
-
            path: "/git".to_owned().into(),
-
            host: Some("seed.radicle.xyz".to_owned()),
-
            port: Some(8888),
-
            ..git::Url::default()
-
        };
-
        assert_eq!(deserialize::<git::Url>(&serialize(&url)).unwrap(), url);
-
    }
-
}
added node/src/service.rs
@@ -0,0 +1,888 @@
+
#![allow(dead_code)]
+
pub mod config;
+
pub mod filter;
+
pub mod message;
+
pub mod peer;
+
pub mod wire;
+

+
use std::ops::{Deref, DerefMut};
+
use std::{collections::VecDeque, fmt, net, net::IpAddr};
+

+
use crossbeam_channel as chan;
+
use fastrand::Rng;
+
use git_url::Url;
+
use log::*;
+
use nakamoto::{LocalDuration, LocalTime};
+
use nakamoto_net as nakamoto;
+
use nakamoto_net::Link;
+
use nonempty::NonEmpty;
+

+
use crate::address_book;
+
use crate::address_book::AddressBook;
+
use crate::address_manager::AddressManager;
+
use crate::clock::RefClock;
+
use crate::collections::{HashMap, HashSet};
+
use crate::crypto;
+
use crate::identity::{Id, Project};
+
use crate::service::config::ProjectTracking;
+
use crate::service::message::{NodeAnnouncement, RefsAnnouncement};
+
use crate::service::peer::{Peer, PeerError, PeerState};
+
use crate::storage;
+
use crate::storage::{Inventory, ReadRepository, RefUpdate, WriteRepository, WriteStorage};
+

+
pub use crate::service::config::{Config, Network};
+
pub use crate::service::message::{Envelope, Message};
+

+
use self::filter::Filter;
+
use self::message::{InventoryAnnouncement, NodeFeatures};
+

+
pub const DEFAULT_PORT: u16 = 8776;
+
pub const PROTOCOL_VERSION: u32 = 1;
+
pub const TARGET_OUTBOUND_PEERS: usize = 8;
+
pub const IDLE_INTERVAL: LocalDuration = LocalDuration::from_secs(30);
+
pub const ANNOUNCE_INTERVAL: LocalDuration = LocalDuration::from_secs(30);
+
pub const SYNC_INTERVAL: LocalDuration = LocalDuration::from_secs(60);
+
pub const PRUNE_INTERVAL: LocalDuration = LocalDuration::from_mins(30);
+
pub const MAX_CONNECTION_ATTEMPTS: usize = 3;
+
pub const MAX_TIME_DELTA: LocalDuration = LocalDuration::from_mins(60);
+

+
/// Network node identifier.
+
pub type NodeId = crypto::PublicKey;
+
/// Network routing table. Keeps track of where projects are hosted.
+
pub type Routing = HashMap<Id, HashSet<NodeId>>;
+
/// Seconds since epoch.
+
pub type Timestamp = u64;
+

+
/// Output of a state transition.
+
#[derive(Debug)]
+
pub enum Io {
+
    /// There are some messages ready to be sent to a peer.
+
    Write(net::SocketAddr, Vec<Envelope>),
+
    /// Connect to a peer.
+
    Connect(net::SocketAddr),
+
    /// Disconnect from a peer.
+
    Disconnect(net::SocketAddr, DisconnectReason),
+
    /// Ask for a wakeup in a specified amount of time.
+
    Wakeup(LocalDuration),
+
    /// Emit an event.
+
    Event(Event),
+
}
+

+
/// A service event.
+
#[derive(Debug, Clone)]
+
pub enum Event {
+
    RefsFetched {
+
        from: Url,
+
        project: Id,
+
        updated: Vec<RefUpdate>,
+
    },
+
}
+

+
/// Error returned by [`Command::Fetch`].
+
#[derive(thiserror::Error, Debug)]
+
pub enum FetchError {
+
    #[error(transparent)]
+
    Git(#[from] git2::Error),
+
    #[error(transparent)]
+
    Storage(#[from] storage::Error),
+
    #[error(transparent)]
+
    Fetch(#[from] storage::FetchError),
+
}
+

+
/// Result of looking up seeds in our routing table.
+
#[derive(Debug)]
+
pub enum FetchLookup {
+
    /// Found seeds for the given project.
+
    Found {
+
        seeds: NonEmpty<net::SocketAddr>,
+
        results: chan::Receiver<FetchResult>,
+
    },
+
    /// Can't fetch because no seeds were found for this project.
+
    NotFound,
+
    /// Can't fetch because the project isn't tracked.
+
    NotTracking,
+
    /// Error trying to find seeds.
+
    Error(FetchError),
+
}
+

+
/// Result of a fetch request from a specific seed.
+
#[derive(Debug)]
+
#[allow(clippy::large_enum_variant)]
+
pub enum FetchResult {
+
    /// Successful fetch from a seed.
+
    Fetched {
+
        from: net::SocketAddr,
+
        updated: Vec<RefUpdate>,
+
    },
+
    /// Error fetching the resource from a seed.
+
    Error {
+
        from: net::SocketAddr,
+
        error: FetchError,
+
    },
+
}
+

+
/// Commands sent to the service by the operator.
+
#[derive(Debug)]
+
pub enum Command {
+
    AnnounceRefs(Id),
+
    Connect(net::SocketAddr),
+
    Fetch(Id, chan::Sender<FetchLookup>),
+
    Track(Id, chan::Sender<bool>),
+
    Untrack(Id, chan::Sender<bool>),
+
}
+

+
/// Command-related errors.
+
#[derive(thiserror::Error, Debug)]
+
pub enum CommandError {}
+

+
#[derive(Debug)]
+
pub struct Service<S, T, G> {
+
    /// Peers currently or recently connected.
+
    peers: Peers,
+
    /// Service state that isn't peer-specific.
+
    context: Context<S, T, G>,
+
    /// Whether our local inventory no long represents what we have announced to the network.
+
    out_of_sync: bool,
+
    /// Last time the service was idle.
+
    last_idle: LocalTime,
+
    /// Last time the service synced.
+
    last_sync: LocalTime,
+
    /// Last time the service routing table was pruned.
+
    last_prune: LocalTime,
+
    /// Last time the service announced its inventory.
+
    last_announce: LocalTime,
+
    /// Time when the service was initialized.
+
    start_time: LocalTime,
+
}
+

+
impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service<S, T, G> {
+
    pub fn new(
+
        config: Config,
+
        clock: RefClock,
+
        storage: T,
+
        addresses: S,
+
        signer: G,
+
        rng: Rng,
+
    ) -> Self {
+
        let addrmgr = AddressManager::new(addresses);
+

+
        Self {
+
            context: Context::new(config, clock, storage, addrmgr, signer, rng.clone()),
+
            peers: Peers::new(rng),
+
            out_of_sync: false,
+
            last_idle: LocalTime::default(),
+
            last_sync: LocalTime::default(),
+
            last_prune: LocalTime::default(),
+
            last_announce: LocalTime::default(),
+
            start_time: LocalTime::default(),
+
        }
+
    }
+

+
    pub fn disconnect(&mut self, remote: &IpAddr, reason: DisconnectReason) {
+
        if let Some(addr) = self.peers.get(remote).map(|p| p.addr) {
+
            self.context.disconnect(addr, reason);
+
        }
+
    }
+

+
    pub fn seeds(&self, id: &Id) -> Box<dyn Iterator<Item = (&NodeId, &Peer)> + '_> {
+
        if let Some(peers) = self.routing.get(id) {
+
            Box::new(
+
                peers
+
                    .iter()
+
                    .filter_map(|id| self.peers.by_id(id).map(|p| (id, p))),
+
            )
+
        } else {
+
            Box::new(std::iter::empty())
+
        }
+
    }
+

+
    pub fn tracked(&self) -> Result<Vec<Id>, storage::Error> {
+
        let tracked = match &self.config.project_tracking {
+
            ProjectTracking::All { blocked } => self
+
                .storage
+
                .inventory()?
+
                .into_iter()
+
                .filter(|id| !blocked.contains(id))
+
                .collect(),
+

+
            ProjectTracking::Allowed(projs) => projs.iter().cloned().collect(),
+
        };
+

+
        Ok(tracked)
+
    }
+

+
    /// Track a project.
+
    /// Returns whether or not the tracking policy was updated.
+
    pub fn track(&mut self, id: Id) -> bool {
+
        self.out_of_sync = self.config.track(id);
+
        self.out_of_sync
+
    }
+

+
    /// Untrack a project.
+
    /// Returns whether or not the tracking policy was updated.
+
    /// Note that when untracking, we don't announce anything to the network. This is because by
+
    /// simply not announcing it anymore, it will eventually be pruned by nodes.
+
    pub fn untrack(&mut self, id: Id) -> bool {
+
        self.config.untrack(id)
+
    }
+

+
    /// Find the closest `n` peers by proximity in tracking graphs.
+
    /// Returns a sorted list from the closest peer to the furthest.
+
    /// Peers with more trackings in common score score higher.
+
    #[allow(unused)]
+
    pub fn closest_peers(&self, n: usize) -> Vec<NodeId> {
+
        todo!()
+
    }
+

+
    /// Get the connected peers.
+
    pub fn peers(&self) -> &Peers {
+
        &self.peers
+
    }
+

+
    /// Get the current inventory.
+
    pub fn inventory(&self) -> Result<Inventory, storage::Error> {
+
        self.context.storage.inventory()
+
    }
+

+
    /// Get the storage instance.
+
    pub fn storage(&self) -> &T {
+
        &self.context.storage
+
    }
+

+
    /// Get the mutable storage instance.
+
    pub fn storage_mut(&mut self) -> &mut T {
+
        &mut self.context.storage
+
    }
+

+
    /// Get a project from storage, using the local node's key.
+
    pub fn get(&self, proj: &Id) -> Result<Option<Project>, storage::Error> {
+
        self.storage.get(&self.node_id(), proj)
+
    }
+

+
    /// Get the local signer.
+
    pub fn signer(&self) -> &G {
+
        &self.context.signer
+
    }
+

+
    /// Get the local service time.
+
    pub fn local_time(&self) -> LocalTime {
+
        self.context.clock.local_time()
+
    }
+

+
    /// Get service configuration.
+
    pub fn config(&self) -> &Config {
+
        &self.context.config
+
    }
+

+
    /// Get reference to routing table.
+
    pub fn routing(&self) -> &Routing {
+
        &self.context.routing
+
    }
+

+
    /// Get I/O outbox.
+
    pub fn outbox(&mut self) -> &mut VecDeque<Io> {
+
        &mut self.context.io
+
    }
+

+
    pub fn lookup(&self, id: &Id) -> Lookup {
+
        Lookup {
+
            local: self.context.storage.get(&self.node_id(), id).unwrap(),
+
            remote: self
+
                .context
+
                .routing
+
                .get(id)
+
                .map_or(vec![], |r| r.iter().cloned().collect()),
+
        }
+
    }
+

+
    pub fn initialize(&mut self, time: LocalTime) {
+
        trace!("Init {}", time.as_secs());
+

+
        self.start_time = time;
+

+
        // Connect to configured peers.
+
        let addrs = self.context.config.connect.clone();
+
        for addr in addrs {
+
            self.context.connect(addr);
+
        }
+
    }
+

+
    pub fn tick(&mut self, now: nakamoto::LocalTime) {
+
        trace!("Tick +{}", now - self.start_time);
+

+
        self.context.clock.set(now);
+
    }
+

+
    pub fn wake(&mut self) {
+
        let now = self.context.clock.local_time();
+

+
        trace!("Wake +{}", now - self.start_time);
+

+
        if now - self.last_idle >= IDLE_INTERVAL {
+
            debug!("Running 'idle' task...");
+

+
            self.maintain_connections();
+
            self.context.io.push_back(Io::Wakeup(IDLE_INTERVAL));
+
            self.last_idle = now;
+
        }
+
        if now - self.last_sync >= SYNC_INTERVAL {
+
            debug!("Running 'sync' task...");
+

+
            // TODO: What do we do here?
+
            self.context.io.push_back(Io::Wakeup(SYNC_INTERVAL));
+
            self.last_sync = now;
+
        }
+
        if now - self.last_announce >= ANNOUNCE_INTERVAL {
+
            if self.out_of_sync {
+
                self.announce_inventory().unwrap();
+
            }
+
            self.context.io.push_back(Io::Wakeup(ANNOUNCE_INTERVAL));
+
            self.last_announce = now;
+
        }
+
        if now - self.last_prune >= PRUNE_INTERVAL {
+
            debug!("Running 'prune' task...");
+

+
            self.prune_routing_entries();
+
            self.context.io.push_back(Io::Wakeup(PRUNE_INTERVAL));
+
            self.last_prune = now;
+
        }
+
    }
+

+
    pub fn command(&mut self, cmd: Command) {
+
        debug!("Command {:?}", cmd);
+

+
        match cmd {
+
            Command::Connect(addr) => self.context.connect(addr),
+
            Command::Fetch(id, resp) => {
+
                if !self.config.is_tracking(&id) {
+
                    resp.send(FetchLookup::NotTracking).ok();
+
                    return;
+
                }
+

+
                let seeds = self.seeds(&id).collect::<Vec<_>>();
+
                let seeds = if let Some(seeds) = NonEmpty::from_vec(seeds) {
+
                    seeds
+
                } else {
+
                    log::error!("No seeds found for {}", id);
+
                    resp.send(FetchLookup::NotFound).ok();
+

+
                    return;
+
                };
+
                log::debug!("Found {} seeds for {}", seeds.len(), id);
+

+
                let mut repo = match self.storage.repository(&id) {
+
                    Ok(repo) => repo,
+
                    Err(err) => {
+
                        log::error!("Error opening repo for {}: {}", id, err);
+
                        resp.send(FetchLookup::Error(err.into())).ok();
+

+
                        return;
+
                    }
+
                };
+

+
                let (results_, results) = chan::bounded(seeds.len());
+
                resp.send(FetchLookup::Found {
+
                    seeds: seeds.clone().map(|(_, peer)| peer.addr),
+
                    results,
+
                })
+
                .ok();
+

+
                // TODO: Limit the number of seeds we fetch from? Randomize?
+
                for (_, peer) in seeds {
+
                    match repo.fetch(&Url {
+
                        scheme: git_url::Scheme::Git,
+
                        host: Some(peer.addr.ip().to_string()),
+
                        port: Some(peer.addr.port()),
+
                        // TODO: Fix upstream crate so that it adds a `/` when needed.
+
                        path: format!("/{}", id).into(),
+
                        ..Url::default()
+
                    }) {
+
                        Ok(updated) => {
+
                            results_
+
                                .send(FetchResult::Fetched {
+
                                    from: peer.addr,
+
                                    updated,
+
                                })
+
                                .ok();
+
                        }
+
                        Err(err) => {
+
                            results_
+
                                .send(FetchResult::Error {
+
                                    from: peer.addr,
+
                                    error: err.into(),
+
                                })
+
                                .ok();
+
                        }
+
                    }
+
                }
+
            }
+
            Command::Track(id, resp) => {
+
                resp.send(self.track(id)).ok();
+
            }
+
            Command::Untrack(id, resp) => {
+
                resp.send(self.untrack(id)).ok();
+
            }
+
            Command::AnnounceRefs(id) => {
+
                let node = self.node_id();
+
                let repo = self.storage.repository(&id).unwrap();
+
                let remote = repo.remote(&node).unwrap();
+
                let peers = self.peers.negotiated().map(|(_, p)| p);
+
                let refs = remote.refs.into();
+
                let message = RefsAnnouncement { id, refs };
+
                let signature = message.sign(&self.signer);
+

+
                self.context.broadcast(
+
                    Message::RefsAnnouncement {
+
                        node,
+
                        message,
+
                        signature,
+
                    },
+
                    peers,
+
                );
+
            }
+
        }
+
    }
+

+
    pub fn attempted(&mut self, addr: &std::net::SocketAddr) {
+
        let ip = addr.ip();
+
        let persistent = self.context.config.is_persistent(addr);
+
        let peer = self
+
            .peers
+
            .entry(ip)
+
            .or_insert_with(|| Peer::new(*addr, Link::Outbound, persistent));
+

+
        peer.attempted();
+
    }
+

+
    pub fn connected(
+
        &mut self,
+
        addr: std::net::SocketAddr,
+
        _local_addr: &std::net::SocketAddr,
+
        link: Link,
+
    ) {
+
        let ip = addr.ip();
+

+
        debug!("Connected to {} ({:?})", ip, link);
+

+
        // For outbound connections, we are the first to say "Hello".
+
        // For inbound connections, we wait for the remote to say "Hello" first.
+
        // TODO: How should we deal with multiple peers connecting from the same IP address?
+
        if link.is_outbound() {
+
            // TODO: Refactor this so that we don't create messages if the peer isn't found.
+
            let messages = self.handshake_messages();
+

+
            if let Some(peer) = self.peers.get_mut(&ip) {
+
                self.context.write_all(peer.addr, messages);
+
                peer.connected();
+
            }
+
        } else {
+
            self.peers.insert(
+
                ip,
+
                Peer::new(
+
                    addr,
+
                    Link::Inbound,
+
                    self.context.config.is_persistent(&addr),
+
                ),
+
            );
+
        }
+
    }
+

+
    pub fn disconnected(
+
        &mut self,
+
        addr: &std::net::SocketAddr,
+
        reason: nakamoto::DisconnectReason<DisconnectReason>,
+
    ) {
+
        let since = self.local_time();
+
        let ip = addr.ip();
+

+
        debug!("Disconnected from {} ({})", ip, reason);
+

+
        if let Some(peer) = self.peers.get_mut(&ip) {
+
            peer.state = PeerState::Disconnected { since };
+

+
            // Attempt to re-connect to persistent peers.
+
            if self.context.config.is_persistent(addr) && peer.attempts() < MAX_CONNECTION_ATTEMPTS
+
            {
+
                if reason.is_dial_err() {
+
                    return;
+
                }
+
                if let nakamoto::DisconnectReason::Protocol(r) = reason {
+
                    if !r.is_transient() {
+
                        return;
+
                    }
+
                }
+
                // TODO: Eventually we want a delay before attempting a reconnection,
+
                // with exponential back-off.
+
                debug!("Reconnecting to {} (attempts={})...", ip, peer.attempts());
+

+
                // TODO: Try to reconnect only if the peer was attempted. A disconnect without
+
                // even a successful attempt means that we're unlikely to be able to reconnect.
+

+
                self.context.connect(*addr);
+
            } else {
+
                // TODO: Non-persistent peers should be removed from the
+
                // map here or at some later point.
+
            }
+
        }
+
    }
+

+
    pub fn received_message(&mut self, addr: &std::net::SocketAddr, msg: Envelope) {
+
        let peer_ip = addr.ip();
+
        let peer = if let Some(peer) = self.peers.get_mut(&peer_ip) {
+
            peer
+
        } else {
+
            return;
+
        };
+

+
        let relay = match peer.received(msg, &mut self.context) {
+
            Ok(msg) => msg,
+
            Err(err) => {
+
                self.context
+
                    .disconnect(peer.addr, DisconnectReason::Error(err));
+
                // If there's an error, stop processing messages from this peer.
+
                // However, we still relay messages returned up to this point.
+
                //
+
                // FIXME: The peer should be set in a state such that we don'that
+
                // process further messages.
+
                return;
+
            }
+
        };
+

+
        if let Some(msg) = relay {
+
            let negotiated = self
+
                .peers
+
                .negotiated()
+
                .filter(|(ip, _)| **ip != peer_ip)
+
                .map(|(_, p)| p);
+

+
            self.context.relay(msg, negotiated.clone());
+
        }
+
    }
+

+
    ////////////////////////////////////////////////////////////////////////////
+
    // Periodic tasks
+
    ////////////////////////////////////////////////////////////////////////////
+

+
    /// Announce our inventory to all connected peers.
+
    fn announce_inventory(&mut self) -> Result<(), storage::Error> {
+
        let inv = Message::inventory(self.context.inventory_announcement()?, &self.context.signer);
+

+
        for addr in self.peers.negotiated().map(|(_, p)| p.addr) {
+
            self.context.write(addr, inv.clone());
+
        }
+
        Ok(())
+
    }
+

+
    fn prune_routing_entries(&mut self) {
+
        // TODO
+
    }
+

+
    fn maintain_connections(&mut self) {
+
        // TODO: Connect to all potential seeds.
+
        if self.peers.len() < TARGET_OUTBOUND_PEERS {
+
            let delta = TARGET_OUTBOUND_PEERS - self.peers.len();
+

+
            for _ in 0..delta {
+
                // TODO: Connect to random peer.
+
            }
+
        }
+
    }
+
}
+

+
impl<S, T, G> Deref for Service<S, T, G> {
+
    type Target = Context<S, T, G>;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.context
+
    }
+
}
+

+
impl<S, T, G> DerefMut for Service<S, T, G> {
+
    fn deref_mut(&mut self) -> &mut Self::Target {
+
        &mut self.context
+
    }
+
}
+

+
#[derive(Debug, Clone)]
+
pub enum DisconnectReason {
+
    User,
+
    Error(PeerError),
+
}
+

+
impl DisconnectReason {
+
    fn is_transient(&self) -> bool {
+
        match self {
+
            Self::User => false,
+
            Self::Error(..) => false,
+
        }
+
    }
+
}
+

+
impl From<DisconnectReason> for nakamoto_net::DisconnectReason<DisconnectReason> {
+
    fn from(reason: DisconnectReason) -> Self {
+
        nakamoto_net::DisconnectReason::Protocol(reason)
+
    }
+
}
+

+
impl fmt::Display for DisconnectReason {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        match self {
+
            Self::User => write!(f, "user"),
+
            Self::Error(err) => write!(f, "error: {}", err),
+
        }
+
    }
+
}
+

+
impl<S, T, G> Iterator for Service<S, T, G> {
+
    type Item = Io;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        self.context.io.pop_front()
+
    }
+
}
+

+
/// Result of a project lookup.
+
#[derive(Debug)]
+
pub struct Lookup {
+
    /// Whether the project was found locally or not.
+
    pub local: Option<Project>,
+
    /// A list of remote peers on which the project is known to exist.
+
    pub remote: Vec<NodeId>,
+
}
+

+
/// Global service state used across peers.
+
#[derive(Debug)]
+
pub struct Context<S, T, G> {
+
    /// Service configuration.
+
    config: Config,
+
    /// Our cryptographic signer and key.
+
    signer: G,
+
    /// Tracks the location of projects.
+
    routing: Routing,
+
    /// Outgoing I/O queue.
+
    io: VecDeque<Io>,
+
    /// Clock. Tells the time.
+
    clock: RefClock,
+
    /// Project storage.
+
    storage: T,
+
    /// Peer address manager.
+
    addrmgr: AddressManager<S>,
+
    /// Source of entropy.
+
    rng: Rng,
+
}
+

+
impl<S, T, G> Context<S, T, G>
+
where
+
    T: storage::ReadStorage,
+
    G: crypto::Signer,
+
{
+
    pub(crate) fn node_id(&self) -> NodeId {
+
        *self.signer.public_key()
+
    }
+
}
+

+
impl<'r, S, T, G> Context<S, T, G>
+
where
+
    T: storage::WriteStorage<'r>,
+
    G: crypto::Signer,
+
{
+
    pub(crate) fn new(
+
        config: Config,
+
        clock: RefClock,
+
        storage: T,
+
        addrmgr: AddressManager<S>,
+
        signer: G,
+
        rng: Rng,
+
    ) -> Self {
+
        Self {
+
            config,
+
            signer,
+
            clock,
+
            routing: HashMap::with_hasher(rng.clone().into()),
+
            io: VecDeque::new(),
+
            storage,
+
            addrmgr,
+
            rng,
+
        }
+
    }
+

+
    fn node_announcement(&self) -> NodeAnnouncement {
+
        let timestamp = self.timestamp();
+
        let features = NodeFeatures::default();
+
        let alias = self.alias();
+
        let addresses = vec![]; // TODO
+

+
        NodeAnnouncement {
+
            features,
+
            timestamp,
+
            alias,
+
            addresses,
+
        }
+
    }
+

+
    fn inventory_announcement(&self) -> Result<InventoryAnnouncement, storage::Error> {
+
        let timestamp = self.timestamp();
+
        let inventory = self.storage.inventory()?;
+

+
        Ok(InventoryAnnouncement {
+
            inventory,
+
            timestamp,
+
        })
+
    }
+

+
    fn filter(&self) -> Filter {
+
        match &self.config.project_tracking {
+
            ProjectTracking::All { .. } => Filter::default(),
+
            ProjectTracking::Allowed(ids) => Filter::new(ids.iter()),
+
        }
+
    }
+

+
    fn handshake_messages(&self) -> [Message; 4] {
+
        let git = self.config.git_url.clone();
+
        [
+
            Message::init(
+
                self.node_id(),
+
                self.timestamp(),
+
                self.config.listen.clone(),
+
                git,
+
            ),
+
            Message::node(self.node_announcement(), &self.signer),
+
            Message::inventory(self.inventory_announcement().unwrap(), &self.signer),
+
            Message::subscribe(self.filter(), self.timestamp(), Timestamp::MAX),
+
        ]
+
    }
+

+
    fn alias(&self) -> [u8; 32] {
+
        let mut alias = [0u8; 32];
+

+
        alias[..9].copy_from_slice("anonymous".as_bytes());
+
        alias
+
    }
+

+
    /// 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 {
+
            let inventory = self
+
                .routing
+
                .entry(proj_id.clone())
+
                .or_insert_with(|| HashSet::with_hasher(self.rng.clone().into()));
+

+
            // TODO: Fire an event on routing update.
+
            if inventory.insert(from) && self.config.is_tracking(proj_id) {
+
                self.fetch(proj_id, remote);
+
            }
+
        }
+
    }
+

+
    fn fetch(&mut self, proj_id: &Id, remote: &Url) -> Vec<RefUpdate> {
+
        let mut repo = self.storage.repository(proj_id).unwrap();
+
        let mut path = remote.path.clone();
+

+
        path.push(b'/');
+
        path.extend(proj_id.to_string().into_bytes());
+

+
        repo.fetch(&Url {
+
            path,
+
            ..remote.clone()
+
        })
+
        .unwrap()
+
    }
+

+
    /// Disconnect a peer.
+
    fn disconnect(&mut self, addr: net::SocketAddr, reason: DisconnectReason) {
+
        self.io.push_back(Io::Disconnect(addr, reason));
+
    }
+
}
+

+
impl<S, T, G> Context<S, T, G> {
+
    /// Get current local timestamp.
+
    pub(crate) fn timestamp(&self) -> Timestamp {
+
        self.clock.local_time().as_secs()
+
    }
+

+
    /// Connect to a peer.
+
    fn connect(&mut self, addr: net::SocketAddr) {
+
        // TODO: Make sure we don't try to connect more than once to the same address.
+
        self.io.push_back(Io::Connect(addr));
+
    }
+

+
    fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) {
+
        let envelopes = msgs
+
            .into_iter()
+
            .map(|msg| self.config.network.envelope(msg))
+
            .collect();
+
        self.io.push_back(Io::Write(remote, envelopes));
+
    }
+

+
    fn write(&mut self, remote: net::SocketAddr, msg: Message) {
+
        debug!("Write {:?} to {}", &msg, remote.ip());
+

+
        let envelope = self.config.network.envelope(msg);
+
        self.io.push_back(Io::Write(remote, vec![envelope]));
+
    }
+

+
    /// Broadcast a message to a list of peers.
+
    fn broadcast<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Peer>) {
+
        for peer in peers {
+
            self.write(peer.addr, msg.clone());
+
        }
+
    }
+

+
    /// Relay a message to interested peers.
+
    fn relay<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Peer>) {
+
        if let Message::RefsAnnouncement { message, .. } = &msg {
+
            let id = message.id.clone();
+
            let peers = peers.into_iter().filter(|p| {
+
                if let Some(subscribe) = &p.subscribe {
+
                    subscribe.filter.contains(&id)
+
                } else {
+
                    // If the peer did not send us a `subscribe` message, we don'the
+
                    // relay any messages to them.
+
                    false
+
                }
+
            });
+
            self.broadcast(msg, peers);
+
        } else {
+
            self.broadcast(msg, peers);
+
        }
+
    }
+
}
+

+
#[derive(Debug)]
+
/// Holds currently (or recently) connected peers.
+
pub struct Peers(AddressBook<IpAddr, Peer>);
+

+
impl Peers {
+
    pub fn new(rng: Rng) -> Self {
+
        Self(AddressBook::new(rng))
+
    }
+

+
    pub fn by_id(&self, id: &NodeId) -> Option<&Peer> {
+
        self.0.values().find(|p| {
+
            if let PeerState::Negotiated { id: _id, .. } = &p.state {
+
                _id == id
+
            } else {
+
                false
+
            }
+
        })
+
    }
+

+
    /// Iterator over fully negotiated peers.
+
    pub fn negotiated(&self) -> impl Iterator<Item = (&IpAddr, &Peer)> + Clone {
+
        self.0.iter().filter(move |(_, p)| p.is_negotiated())
+
    }
+
}
+

+
impl Deref for Peers {
+
    type Target = AddressBook<IpAddr, Peer>;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.0
+
    }
+
}
+

+
impl DerefMut for Peers {
+
    fn deref_mut(&mut self) -> &mut Self::Target {
+
        &mut self.0
+
    }
+
}
added node/src/service/config.rs
@@ -0,0 +1,128 @@
+
use std::net;
+

+
use git_url::Url;
+

+
use crate::collections::HashSet;
+
use crate::git;
+
use crate::identity::{Id, PublicKey};
+
use crate::service::message::{Address, Envelope, Message};
+

+
/// Peer-to-peer network.
+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+
pub enum Network {
+
    #[default]
+
    Main,
+
    Test,
+
}
+

+
impl Network {
+
    pub fn magic(&self) -> u32 {
+
        match self {
+
            Self::Main => 0x819b43d9,
+
            Self::Test => 0x717ebaf8,
+
        }
+
    }
+

+
    pub fn envelope(&self, msg: Message) -> Envelope {
+
        Envelope {
+
            magic: self.magic(),
+
            msg,
+
        }
+
    }
+
}
+

+
/// Project tracking policy.
+
#[derive(Debug, Clone)]
+
pub enum ProjectTracking {
+
    /// Track all projects we come across.
+
    All { blocked: HashSet<Id> },
+
    /// Track a static list of projects.
+
    Allowed(HashSet<Id>),
+
}
+

+
impl Default for ProjectTracking {
+
    fn default() -> Self {
+
        Self::All {
+
            blocked: HashSet::default(),
+
        }
+
    }
+
}
+

+
/// Project remote tracking policy.
+
#[derive(Debug, Default, Clone)]
+
pub enum RemoteTracking {
+
    /// Only track remotes of project delegates.
+
    #[default]
+
    DelegatesOnly,
+
    /// Track all remotes.
+
    All { blocked: HashSet<PublicKey> },
+
    /// Track a specific list of users as well as the project delegates.
+
    Allowed(HashSet<PublicKey>),
+
}
+

+
/// Service configuration.
+
#[derive(Debug, Clone)]
+
pub struct Config {
+
    /// Peers to connect to on startup.
+
    /// Connections to these peers will be maintained.
+
    pub connect: Vec<net::SocketAddr>,
+
    /// Peer-to-peer network.
+
    pub network: Network,
+
    /// Project tracking policy.
+
    pub project_tracking: ProjectTracking,
+
    /// Project remote tracking policy.
+
    pub remote_tracking: RemoteTracking,
+
    /// Whether or not our node should relay inventories.
+
    pub relay: bool,
+
    /// List of addresses to listen on for protocol connections.
+
    pub listen: Vec<Address>,
+
    /// Our Git URL for fetching projects.
+
    pub git_url: Url,
+
}
+

+
impl Default for Config {
+
    fn default() -> Self {
+
        Self {
+
            connect: Vec::default(),
+
            network: Network::default(),
+
            project_tracking: ProjectTracking::default(),
+
            remote_tracking: RemoteTracking::default(),
+
            relay: true,
+
            listen: vec![],
+
            git_url: Url {
+
                scheme: git::url::Scheme::File,
+
                path: "/dev/null".to_owned().into(),
+
                ..Url::default()
+
            },
+
        }
+
    }
+
}
+

+
impl Config {
+
    pub fn is_persistent(&self, addr: &net::SocketAddr) -> bool {
+
        self.connect.contains(addr)
+
    }
+

+
    pub fn is_tracking(&self, id: &Id) -> bool {
+
        match &self.project_tracking {
+
            ProjectTracking::All { blocked } => !blocked.contains(id),
+
            ProjectTracking::Allowed(ids) => ids.contains(id),
+
        }
+
    }
+

+
    /// Track a project. Returns whether the policy was updated.
+
    pub fn track(&mut self, id: Id) -> bool {
+
        match &mut self.project_tracking {
+
            ProjectTracking::All { .. } => false,
+
            ProjectTracking::Allowed(ids) => ids.insert(id),
+
        }
+
    }
+

+
    /// Untrack a project. Returns whether the policy was updated.
+
    pub fn untrack(&mut self, id: Id) -> bool {
+
        match &mut self.project_tracking {
+
            ProjectTracking::All { blocked } => blocked.insert(id),
+
            ProjectTracking::Allowed(ids) => ids.remove(&id),
+
        }
+
    }
+
}
added node/src/service/filter.rs
@@ -0,0 +1,84 @@
+
use std::io;
+
use std::ops::{Deref, DerefMut};
+

+
use bloomy::BloomFilter;
+

+
use crate::identity::Id;
+
use crate::service::wire;
+

+
/// Size in bytes of subscription bloom filter.
+
pub const FILTER_SIZE: usize = 1024 * 16;
+
/// Number of hashes used for bloom filter.
+
pub const FILTER_HASHES: usize = 7;
+

+
/// Subscription filter.
+
///
+
/// The [`Default`] instance has all bits set to `1`, ie. it will match
+
/// everything.
+
///
+
/// Nb. This filter doesn't currently support inserting public keys.
+
#[derive(Clone, PartialEq, Eq, Debug)]
+
pub struct Filter(BloomFilter<Id>);
+

+
impl Default for Filter {
+
    fn default() -> Self {
+
        Self(BloomFilter::from(vec![0xff; FILTER_SIZE]))
+
    }
+
}
+

+
impl Filter {
+
    pub fn new<'a>(ids: impl IntoIterator<Item = &'a Id>) -> Self {
+
        let mut bloom = BloomFilter::with_size(FILTER_SIZE);
+

+
        for id in ids.into_iter() {
+
            bloom.insert(id);
+
        }
+
        Self(bloom)
+
    }
+
}
+

+
impl Deref for Filter {
+
    type Target = BloomFilter<Id>;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.0
+
    }
+
}
+

+
impl DerefMut for Filter {
+
    fn deref_mut(&mut self) -> &mut Self::Target {
+
        &mut self.0
+
    }
+
}
+

+
#[cfg(test)]
+
impl From<BloomFilter<Id>> for Filter {
+
    fn from(bloom: BloomFilter<Id>) -> Self {
+
        Self(bloom)
+
    }
+
}
+

+
impl wire::Encode for Filter {
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        let mut n = 0;
+

+
        n += self.0.as_bytes().encode(writer)?;
+

+
        Ok(n)
+
    }
+
}
+

+
impl wire::Decode for Filter {
+
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let size: wire::Size = wire::Decode::decode(reader)?;
+
        if size as usize != FILTER_SIZE {
+
            return Err(wire::Error::InvalidFilterSize(size as usize));
+
        }
+
        let bytes: [u8; FILTER_SIZE] = wire::Decode::decode(reader)?;
+
        let bf = BloomFilter::from(Vec::from(bytes));
+

+
        debug_assert_eq!(bf.hashes(), FILTER_HASHES);
+

+
        Ok(Self(bf))
+
    }
+
}
added node/src/service/message.rs
@@ -0,0 +1,663 @@
+
use std::{fmt, io, net};
+

+
use byteorder::{NetworkEndian, ReadBytesExt};
+

+
use crate::crypto;
+
use crate::git;
+
use crate::identity::Id;
+
use crate::service::filter::Filter;
+
use crate::service::wire;
+
use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION};
+
use crate::storage::refs::Refs;
+

+
/// 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);
+

+
/// Message type.
+
#[repr(u16)]
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+
pub enum MessageType {
+
    Initialize = 0,
+
    NodeAnnouncement = 2,
+
    InventoryAnnouncement = 4,
+
    RefsAnnouncement = 6,
+
    Subscribe = 8,
+
}
+

+
impl From<MessageType> for u16 {
+
    fn from(other: MessageType) -> Self {
+
        other as u16
+
    }
+
}
+

+
impl TryFrom<u16> for MessageType {
+
    type Error = u16;
+

+
    fn try_from(other: u16) -> Result<Self, Self::Error> {
+
        match other {
+
            0 => Ok(MessageType::Initialize),
+
            2 => Ok(MessageType::NodeAnnouncement),
+
            4 => Ok(MessageType::InventoryAnnouncement),
+
            6 => Ok(MessageType::RefsAnnouncement),
+
            8 => Ok(MessageType::Subscribe),
+
            _ => Err(other),
+
        }
+
    }
+
}
+

+
/// Address type.
+
#[repr(u8)]
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+
pub enum AddressType {
+
    Ipv4 = 1,
+
    Ipv6 = 2,
+
    Hostname = 3,
+
    Onion = 4,
+
}
+

+
impl From<AddressType> for u8 {
+
    fn from(other: AddressType) -> Self {
+
        other as u8
+
    }
+
}
+

+
impl TryFrom<u8> for AddressType {
+
    type Error = u8;
+

+
    fn try_from(other: u8) -> Result<Self, Self::Error> {
+
        match other {
+
            1 => Ok(AddressType::Ipv4),
+
            2 => Ok(AddressType::Ipv6),
+
            3 => Ok(AddressType::Hostname),
+
            4 => Ok(AddressType::Onion),
+
            _ => Err(other),
+
        }
+
    }
+
}
+

+
/// 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<net::SocketAddr> 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 },
+
        }
+
    }
+
}
+

+
impl wire::Encode for Envelope {
+
    fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
+
        let mut n = 0;
+

+
        n += self.magic.encode(writer)?;
+
        n += self.msg.encode(writer)?;
+

+
        Ok(n)
+
    }
+
}
+

+
impl wire::Decode for Envelope {
+
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let magic = u32::decode(reader)?;
+
        let msg = Message::decode(reader)?;
+

+
        Ok(Self { magic, msg })
+
    }
+
}
+

+
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::Ipv4 { ip, port } => {
+
                n += u8::from(AddressType::Ipv4).encode(writer)?;
+
                n += ip.octets().encode(writer)?;
+
                n += port.encode(writer)?;
+
            }
+
            Self::Ipv6 { ip, port } => {
+
                n += u8::from(AddressType::Ipv6).encode(writer)?;
+
                n += ip.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> {
+
        let addrtype = reader.read_u8()?;
+

+
        match AddressType::try_from(addrtype) {
+
            Ok(AddressType::Ipv4) => {
+
                let octets: [u8; 4] = wire::Decode::decode(reader)?;
+
                let ip = net::Ipv4Addr::from(octets);
+
                let port = u16::decode(reader)?;
+

+
                Ok(Self::Ipv4 { ip, port })
+
            }
+
            Ok(AddressType::Ipv6) => {
+
                let octets: [u8; 16] = wire::Decode::decode(reader)?;
+
                let ip = net::Ipv6Addr::from(octets);
+
                let port = u16::decode(reader)?;
+

+
                Ok(Self::Ipv6 { ip, port })
+
            }
+
            Ok(AddressType::Hostname) => {
+
                todo!();
+
            }
+
            Ok(AddressType::Onion) => {
+
                todo!();
+
            }
+
            Err(other) => Err(wire::Error::UnknownAddressType(other)),
+
        }
+
    }
+
}
+

+
#[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<Address>,
+
}
+

+
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<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        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<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let features = NodeFeatures::decode(reader)?;
+
        let timestamp = Timestamp::decode(reader)?;
+
        let alias = wire::Decode::decode(reader)?;
+
        let addresses = Vec::<Address>::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<S: crypto::Signer>(&self, signer: S) -> crypto::Signature {
+
        let msg = wire::serialize(self);
+
        signer.sign(&msg)
+
    }
+
}
+

+
impl wire::Encode for RefsAnnouncement {
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        let mut n = 0;
+

+
        n += self.id.encode(writer)?;
+
        n += self.refs.encode(writer)?;
+

+
        Ok(n)
+
    }
+
}
+

+
impl wire::Decode for RefsAnnouncement {
+
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let id = Id::decode(reader)?;
+
        let refs = Refs::decode(reader)?;
+

+
        Ok(Self { id, refs })
+
    }
+
}
+

+
#[derive(Debug, Clone, PartialEq, Eq)]
+
pub struct InventoryAnnouncement {
+
    pub inventory: Vec<Id>,
+
    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()
+
    }
+
}
+

+
impl wire::Encode for InventoryAnnouncement {
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        let mut n = 0;
+

+
        n += self.inventory.as_slice().encode(writer)?;
+
        n += self.timestamp.encode(writer)?;
+

+
        Ok(n)
+
    }
+
}
+

+
impl wire::Decode for InventoryAnnouncement {
+
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let inventory = Vec::<Id>::decode(reader)?;
+
        let timestamp = Timestamp::decode(reader)?;
+

+
        Ok(Self {
+
            inventory,
+
            timestamp,
+
        })
+
    }
+
}
+

+
/// 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<Address>,
+
        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<Address>, git: git::Url) -> Self {
+
        Self::Initialize {
+
            id,
+
            timestamp,
+
            version: PROTOCOL_VERSION,
+
            addrs,
+
            git,
+
        }
+
    }
+

+
    pub fn node<S: crypto::Signer>(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<S: crypto::Signer>(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,
+
        })
+
    }
+

+
    pub fn type_id(&self) -> u16 {
+
        match self {
+
            Self::Initialize { .. } => MessageType::Initialize,
+
            Self::Subscribe { .. } => MessageType::Subscribe,
+
            Self::NodeAnnouncement { .. } => MessageType::NodeAnnouncement,
+
            Self::InventoryAnnouncement { .. } => MessageType::InventoryAnnouncement,
+
            Self::RefsAnnouncement { .. } => MessageType::RefsAnnouncement,
+
        }
+
        .into()
+
    }
+
}
+

+
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::<Vec<String>>()
+
                        .join(", "),
+
                    message.timestamp
+
                )
+
            }
+
            Self::RefsAnnouncement { node, message, .. } => {
+
                write!(
+
                    f,
+
                    "RefsAnnouncement({}, {}, {:?})",
+
                    node, message.id, message.refs
+
                )
+
            }
+
        }
+
    }
+
}
+

+
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::Initialize {
+
                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::Subscribe(Subscribe {
+
                filter,
+
                since,
+
                until,
+
            }) => {
+
                n += filter.encode(writer)?;
+
                n += since.encode(writer)?;
+
                n += until.encode(writer)?;
+
            }
+
            Self::RefsAnnouncement {
+
                node,
+
                message,
+
                signature,
+
            } => {
+
                n += node.encode(writer)?;
+
                n += message.encode(writer)?;
+
                n += signature.encode(writer)?;
+
            }
+
            Self::InventoryAnnouncement {
+
                node,
+
                message,
+
                signature,
+
            } => {
+
                n += node.encode(writer)?;
+
                n += message.encode(writer)?;
+
                n += signature.encode(writer)?;
+
            }
+
            Self::NodeAnnouncement {
+
                node,
+
                message,
+
                signature,
+
            } => {
+
                n += node.encode(writer)?;
+
                n += message.encode(writer)?;
+
                n += signature.encode(writer)?;
+
            }
+
        }
+
        Ok(n)
+
    }
+
}
+

+
impl wire::Decode for Message {
+
    fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
+
        let type_id = reader.read_u16::<NetworkEndian>()?;
+

+
        match MessageType::try_from(type_id) {
+
            Ok(MessageType::Initialize) => {
+
                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::Initialize {
+
                    id,
+
                    timestamp,
+
                    version,
+
                    addrs,
+
                    git,
+
                })
+
            }
+
            Ok(MessageType::Subscribe) => {
+
                let filter = Filter::decode(reader)?;
+
                let since = Timestamp::decode(reader)?;
+
                let until = Timestamp::decode(reader)?;
+

+
                Ok(Self::Subscribe(Subscribe {
+
                    filter,
+
                    since,
+
                    until,
+
                }))
+
            }
+
            Ok(MessageType::NodeAnnouncement) => {
+
                let node = NodeId::decode(reader)?;
+
                let message = NodeAnnouncement::decode(reader)?;
+
                let signature = crypto::Signature::decode(reader)?;
+

+
                Ok(Self::NodeAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                })
+
            }
+
            Ok(MessageType::InventoryAnnouncement) => {
+
                let node = NodeId::decode(reader)?;
+
                let message = InventoryAnnouncement::decode(reader)?;
+
                let signature = crypto::Signature::decode(reader)?;
+

+
                Ok(Self::InventoryAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                })
+
            }
+
            Ok(MessageType::RefsAnnouncement) => {
+
                let node = NodeId::decode(reader)?;
+
                let message = RefsAnnouncement::decode(reader)?;
+
                let signature = crypto::Signature::decode(reader)?;
+

+
                Ok(Self::RefsAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                })
+
            }
+
            Err(other) => Err(wire::Error::UnknownMessageType(other)),
+
        }
+
    }
+
}
+

+
#[cfg(test)]
+
mod tests {
+
    use super::*;
+
    use quickcheck_macros::quickcheck;
+

+
    use crate::crypto::Signer;
+
    use crate::decoder::Decoder;
+
    use crate::service::wire::{self, Encode};
+
    use crate::test::crypto::MockSigner;
+

+
    #[quickcheck]
+
    fn prop_message_encode_decode(message: Message) {
+
        assert_eq!(
+
            wire::deserialize::<Message>(&wire::serialize(&message)).unwrap(),
+
            message
+
        );
+
    }
+

+
    #[quickcheck]
+
    fn prop_envelope_encode_decode(envelope: Envelope) {
+
        assert_eq!(
+
            wire::deserialize::<Envelope>(&wire::serialize(&envelope)).unwrap(),
+
            envelope
+
        );
+
    }
+

+
    #[test]
+
    fn prop_envelope_decoder() {
+
        fn property(items: Vec<Envelope>) {
+
            let mut decoder = Decoder::<Envelope>::new(8);
+

+
            for item in &items {
+
                item.encode(&mut decoder).unwrap();
+
            }
+
            for item in items {
+
                assert_eq!(decoder.next().unwrap().unwrap(), item);
+
            }
+
        }
+

+
        quickcheck::QuickCheck::new()
+
            .gen(quickcheck::Gen::new(16))
+
            .quickcheck(property as fn(items: Vec<Envelope>));
+
    }
+

+
    #[quickcheck]
+
    fn prop_addr(addr: Address) {
+
        assert_eq!(
+
            wire::deserialize::<Address>(&wire::serialize(&addr)).unwrap(),
+
            addr
+
        );
+
    }
+

+
    #[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));
+
    }
+
}
added node/src/service/peer.rs
@@ -0,0 +1,244 @@
+
use crate::service::message::*;
+
use crate::service::*;
+

+
#[derive(Debug, Default)]
+
#[allow(clippy::large_enum_variant)]
+
pub enum PeerState {
+
    /// Initial peer state. For outgoing peers this
+
    /// means we've attempted a connection. For incoming
+
    /// peers, this means they've successfully connected
+
    /// to us.
+
    #[default]
+
    Initial,
+
    /// State after successful handshake.
+
    Negotiated {
+
        /// The peer's unique identifier.
+
        id: NodeId,
+
        since: LocalTime,
+
        /// Addresses this peer is reachable on.
+
        addrs: Vec<Address>,
+
        git: Url,
+
    },
+
    /// When a peer is disconnected.
+
    Disconnected { since: LocalTime },
+
}
+

+
#[derive(thiserror::Error, Debug, Clone)]
+
pub enum PeerError {
+
    #[error("wrong network constant in message: {0}")]
+
    WrongMagic(u32),
+
    #[error("wrong protocol version in message: {0}")]
+
    WrongVersion(u32),
+
    #[error("invalid inventory timestamp: {0}")]
+
    InvalidTimestamp(u64),
+
    #[error("peer misbehaved")]
+
    Misbehavior,
+
}
+

+
#[derive(Debug)]
+
pub struct Peer {
+
    /// Peer address.
+
    pub addr: net::SocketAddr,
+
    /// Connection direction.
+
    pub link: Link,
+
    /// Whether we should attempt to re-connect
+
    /// to this peer upon disconnection.
+
    pub persistent: bool,
+
    /// Peer connection state.
+
    pub state: PeerState,
+
    /// Last known peer time.
+
    pub timestamp: Timestamp,
+
    /// Peer subscription.
+
    pub subscribe: Option<Subscribe>,
+

+
    /// Connection attempts. For persistent peers, Tracks
+
    /// how many times we've attempted to connect. We reset this to zero
+
    /// upon successful connection.
+
    attempts: usize,
+
}
+

+
impl Peer {
+
    pub fn new(addr: net::SocketAddr, link: Link, persistent: bool) -> Self {
+
        Self {
+
            addr,
+
            state: PeerState::default(),
+
            link,
+
            timestamp: Timestamp::default(),
+
            subscribe: None,
+
            persistent,
+
            attempts: 0,
+
        }
+
    }
+

+
    pub fn ip(&self) -> IpAddr {
+
        self.addr.ip()
+
    }
+

+
    pub fn is_negotiated(&self) -> bool {
+
        matches!(self.state, PeerState::Negotiated { .. })
+
    }
+

+
    pub fn attempts(&self) -> usize {
+
        self.attempts
+
    }
+

+
    pub fn attempted(&mut self) {
+
        self.attempts += 1;
+
    }
+

+
    pub fn connected(&mut self) {
+
        self.attempts = 0;
+
    }
+

+
    pub fn received<'r, S, T, G>(
+
        &mut self,
+
        envelope: Envelope,
+
        ctx: &mut Context<S, T, G>,
+
    ) -> Result<Option<Message>, PeerError>
+
    where
+
        T: storage::WriteStorage<'r>,
+
        G: crypto::Signer,
+
    {
+
        if envelope.magic != ctx.config.network.magic() {
+
            return Err(PeerError::WrongMagic(envelope.magic));
+
        }
+
        debug!("Received {:?} from {}", &envelope.msg, self.ip());
+

+
        match (&self.state, envelope.msg) {
+
            (
+
                PeerState::Initial,
+
                Message::Initialize {
+
                    id,
+
                    timestamp,
+
                    version,
+
                    addrs,
+
                    git,
+
                },
+
            ) => {
+
                let now = ctx.timestamp();
+

+
                if timestamp.abs_diff(now) > MAX_TIME_DELTA.as_secs() {
+
                    return Err(PeerError::InvalidTimestamp(timestamp));
+
                }
+
                if version != PROTOCOL_VERSION {
+
                    return Err(PeerError::WrongVersion(version));
+
                }
+
                // Nb. This is a very primitive handshake. Eventually we should have anyhow
+
                // extra "acknowledgment" message sent when the `Initialize` is well received.
+
                if self.link.is_inbound() {
+
                    ctx.write_all(self.addr, ctx.handshake_messages());
+
                }
+
                // Nb. we don't set the peer timestamp here, since it is going to be
+
                // set after the first message is received only. Setting it here would
+
                // mean that messages received right after the handshake could be ignored.
+
                self.state = PeerState::Negotiated {
+
                    id,
+
                    since: ctx.clock.local_time(),
+
                    addrs,
+
                    git,
+
                };
+
            }
+
            (PeerState::Initial, _) => {
+
                debug!(
+
                    "Disconnecting peer {} for sending us a message before handshake",
+
                    self.ip()
+
                );
+
                return Err(PeerError::Misbehavior);
+
            }
+
            (
+
                PeerState::Negotiated { git, .. },
+
                Message::InventoryAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                },
+
            ) => {
+
                let now = ctx.clock.local_time();
+
                let last = self.timestamp;
+

+
                // Don't allow messages from too far in the past or future.
+
                if message.timestamp.abs_diff(now.as_secs()) > MAX_TIME_DELTA.as_secs() {
+
                    return Err(PeerError::InvalidTimestamp(message.timestamp));
+
                }
+
                // Discard inventory messages we've already seen, otherwise update
+
                // out last seen time.
+
                if message.timestamp > last {
+
                    self.timestamp = message.timestamp;
+
                } else {
+
                    return Ok(None);
+
                }
+
                ctx.process_inventory(&message.inventory, node, git);
+

+
                if ctx.config.relay {
+
                    return Ok(Some(Message::InventoryAnnouncement {
+
                        node,
+
                        message,
+
                        signature,
+
                    }));
+
                }
+
            }
+
            // Process a peer inventory update announcement by (maybe) fetching.
+
            (
+
                PeerState::Negotiated { git, .. },
+
                Message::RefsAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                },
+
            ) => {
+
                if message.verify(&node, &signature) {
+
                    // TODO: Buffer/throttle fetches.
+
                    // TODO: Check that we're tracking this user as well.
+
                    if ctx.config.is_tracking(&message.id) {
+
                        // TODO: Check refs to see if we should try to fetch or not.
+
                        let updated_refs = ctx.fetch(&message.id, git);
+
                        let is_updated = !updated_refs.is_empty();
+

+
                        ctx.io.push_back(Io::Event(Event::RefsFetched {
+
                            from: git.clone(),
+
                            project: message.id.clone(),
+
                            updated: updated_refs,
+
                        }));
+

+
                        if is_updated {
+
                            return Ok(Some(Message::RefsAnnouncement {
+
                                node,
+
                                message,
+
                                signature,
+
                            }));
+
                        }
+
                    }
+
                } else {
+
                    return Err(PeerError::Misbehavior);
+
                }
+
            }
+
            (
+
                PeerState::Negotiated { .. },
+
                Message::NodeAnnouncement {
+
                    node,
+
                    message,
+
                    signature,
+
                },
+
            ) => {
+
                if !message.verify(&node, &signature) {
+
                    return Err(PeerError::Misbehavior);
+
                }
+
                log::warn!("Node announcement handling is not implemented");
+
            }
+
            (PeerState::Negotiated { .. }, Message::Subscribe(subscribe)) => {
+
                self.subscribe = Some(subscribe);
+
            }
+
            (PeerState::Negotiated { .. }, Message::Initialize { .. }) => {
+
                debug!(
+
                    "Disconnecting peer {} for sending us a redundant handshake message",
+
                    self.ip()
+
                );
+
                return Err(PeerError::Misbehavior);
+
            }
+
            (PeerState::Disconnected { .. }, msg) => {
+
                debug!("Ignoring {:?} from disconnected peer {}", msg, self.ip());
+
            }
+
        }
+
        Ok(None)
+
    }
+
}
added node/src/service/wire.rs
@@ -0,0 +1,633 @@
+
use std::collections::{BTreeMap, HashMap};
+
use std::convert::TryFrom;
+
use std::net::{self, IpAddr};
+
use std::ops::{Deref, DerefMut};
+
use std::string::FromUtf8Error;
+
use std::{io, mem};
+

+
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
+
use nakamoto_net as nakamoto;
+
use nakamoto_net::{Link, LocalTime};
+

+
use crate::address_book;
+
use crate::crypto::{PublicKey, Signature, Signer};
+
use crate::decoder::Decoder;
+
use crate::git;
+
use crate::git::fmt;
+
use crate::hash::Digest;
+
use crate::identity::Id;
+
use crate::service;
+
use crate::storage::refs::Refs;
+
use crate::storage::WriteStorage;
+

+
/// The default type we use to represent sizes.
+
/// Four bytes is more than enough for anything sent over the wire.
+
/// Note that in certain cases, we may use only one or two byte types.
+
pub type Size = u32;
+

+
#[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("invalid filter size: {0}")]
+
    InvalidFilterSize(usize),
+
    #[error(transparent)]
+
    InvalidRefName(#[from] fmt::Error),
+
    #[error("invalid git url `{url}`: {error}")]
+
    InvalidGitUrl {
+
        url: String,
+
        error: git::url::parse::Error,
+
    },
+
    #[error("unknown address type `{0}`")]
+
    UnknownAddressType(u8),
+
    #[error("unknown message type `{0}`")]
+
    UnknownMessageType(u16),
+
}
+

+
impl Error {
+
    /// Whether we've reached the end of file. This will be true when we fail to decode
+
    /// a message because there's not enough data in the stream.
+
    pub fn is_eof(&self) -> bool {
+
        matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
+
    }
+
}
+

+
/// Things that can be encoded as binary.
+
pub trait Encode {
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error>;
+
}
+

+
/// Things that can be decoded from binary.
+
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
+
}
+

+
/// Decode an object from a vector.
+
pub fn deserialize<T: Decode>(data: &[u8]) -> Result<T, Error> {
+
    let mut cursor = io::Cursor::new(data);
+

+
    T::decode(&mut cursor)
+
}
+

+
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 {
+
    /// We encode this type to a [`u32`], since there's no need to send larger messages
+
    /// over the network.
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        assert!(
+
            *self <= u32::MAX as usize,
+
            "Cannot encode sizes larger than {}",
+
            u32::MAX
+
        );
+
        writer.write_u32::<NetworkEndian>(*self as u32)?;
+

+
        Ok(mem::size_of::<u32>())
+
    }
+
}
+

+
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() as Size).encode(writer)?;
+

+
        for item in self.iter() {
+
            n += item.encode(writer)?;
+
        }
+
        Ok(n)
+
    }
+
}
+

+
impl Encode for &str {
+
    fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
+
        assert!(self.len() <= u8::MAX as usize);
+

+
        let n = (self.len() as u8).encode(writer)?;
+
        let bytes = self.as_bytes();
+

+
        // Nb. Don't use the [`Encode`] instance here for &[u8], because we are prefixing the
+
        // length ourselves.
+
        writer.write_all(bytes)?;
+

+
        Ok(n + bytes.len())
+
    }
+
}
+

+
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 = u32::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: Size = Size::decode(reader)?;
+
        let mut vec = Vec::with_capacity(len as usize);
+

+
        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 = u8::decode(reader)?;
+
        let mut bytes = vec![0; len as usize];
+

+
        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 url = String::decode(reader)?;
+
        let url = Self::from_bytes(url.as_bytes())
+
            .map_err(|error| Error::InvalidGitUrl { url, error })?;
+

+
        Ok(url)
+
    }
+
}
+

+
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))
+
    }
+
}
+

+
#[derive(Debug)]
+
pub struct Wire<S, T, G> {
+
    inboxes: HashMap<IpAddr, Decoder>,
+
    inner: service::Service<S, T, G>,
+
}
+

+
impl<S, T, G> Wire<S, T, G> {
+
    pub fn new(inner: service::Service<S, T, G>) -> Self {
+
        Self {
+
            inboxes: HashMap::new(),
+
            inner,
+
        }
+
    }
+
}
+

+
impl<'r, S, T, G> Wire<S, T, G>
+
where
+
    S: address_book::Store,
+
    T: WriteStorage<'r> + 'static,
+
    G: Signer,
+
{
+
    pub fn initialize(&mut self, time: LocalTime) {
+
        self.inner.initialize(time)
+
    }
+

+
    pub fn tick(&mut self, now: LocalTime) {
+
        self.inner.tick(now)
+
    }
+

+
    pub fn wake(&mut self) {
+
        self.inner.wake()
+
    }
+

+
    pub fn command(&mut self, cmd: service::Command) {
+
        self.inner.command(cmd)
+
    }
+

+
    pub fn attempted(&mut self, addr: &net::SocketAddr) {
+
        self.inner.attempted(addr)
+
    }
+

+
    pub fn connected(
+
        &mut self,
+
        addr: std::net::SocketAddr,
+
        local_addr: &std::net::SocketAddr,
+
        link: Link,
+
    ) {
+
        self.inboxes.insert(addr.ip(), Decoder::new(256));
+
        self.inner.connected(addr, local_addr, link)
+
    }
+

+
    pub fn disconnected(
+
        &mut self,
+
        addr: &std::net::SocketAddr,
+
        reason: nakamoto::DisconnectReason<service::DisconnectReason>,
+
    ) {
+
        self.inboxes.remove(&addr.ip());
+
        self.inner.disconnected(addr, reason)
+
    }
+

+
    pub fn received_bytes(&mut self, addr: &std::net::SocketAddr, bytes: &[u8]) {
+
        let peer_ip = addr.ip();
+

+
        if let Some(inbox) = self.inboxes.get_mut(&peer_ip) {
+
            inbox.input(bytes);
+

+
            loop {
+
                match inbox.decode_next() {
+
                    Ok(Some(msg)) => self.inner.received_message(addr, msg),
+
                    Ok(None) => break,
+

+
                    Err(err) => {
+
                        // TODO: Disconnect peer.
+
                        log::error!("Invalid message received from {}: {}", peer_ip, err);
+

+
                        return;
+
                    }
+
                }
+
            }
+
        } else {
+
            log::debug!("Received message from unknown peer {}", peer_ip);
+
        }
+
    }
+
}
+

+
impl<S, T, G> Iterator for Wire<S, T, G> {
+
    type Item = nakamoto::Io<service::Event, service::DisconnectReason>;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        match self.inner.next() {
+
            Some(service::Io::Write(addr, msgs)) => {
+
                let mut buf = Vec::new();
+
                for msg in msgs {
+
                    log::debug!("Write {:?} to {}", &msg, addr.ip());
+

+
                    msg.encode(&mut buf)
+
                        .expect("writing to an in-memory buffer doesn't fail");
+
                }
+
                Some(nakamoto::Io::Write(addr, buf))
+
            }
+
            Some(service::Io::Event(e)) => Some(nakamoto::Io::Event(e)),
+
            Some(service::Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
+
            Some(service::Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
+
            Some(service::Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
+

+
            None => None,
+
        }
+
    }
+
}
+

+
impl<S, T, G> Deref for Wire<S, T, G> {
+
    type Target = service::Service<S, T, G>;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.inner
+
    }
+
}
+
impl<S, T, G> DerefMut for Wire<S, T, G> {
+
    fn deref_mut(&mut self) -> &mut Self::Target {
+
        &mut self.inner
+
    }
+
}
+

+
#[cfg(test)]
+
mod tests {
+
    use super::*;
+
    use quickcheck_macros::quickcheck;
+

+
    use crate::crypto::Unverified;
+
    use crate::storage::refs::SignedRefs;
+
    use crate::test::arbitrary;
+

+
    #[quickcheck]
+
    fn prop_u8(input: u8) {
+
        assert_eq!(deserialize::<u8>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_u16(input: u16) {
+
        assert_eq!(deserialize::<u16>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_u32(input: u32) {
+
        assert_eq!(deserialize::<u32>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_u64(input: u64) {
+
        assert_eq!(deserialize::<u64>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_usize(input: usize) -> quickcheck::TestResult {
+
        if input > u32::MAX as usize {
+
            return quickcheck::TestResult::discard();
+
        }
+
        assert_eq!(deserialize::<usize>(&serialize(&input)).unwrap(), input);
+

+
        quickcheck::TestResult::passed()
+
    }
+

+
    #[quickcheck]
+
    fn prop_string(input: String) -> quickcheck::TestResult {
+
        if input.len() > u8::MAX as usize {
+
            return quickcheck::TestResult::discard();
+
        }
+
        assert_eq!(deserialize::<String>(&serialize(&input)).unwrap(), input);
+

+
        quickcheck::TestResult::passed()
+
    }
+

+
    #[quickcheck]
+
    fn prop_vec(input: Vec<String>) {
+
        assert_eq!(
+
            deserialize::<Vec<String>>(&serialize(&input.as_slice())).unwrap(),
+
            input
+
        );
+
    }
+

+
    #[quickcheck]
+
    fn prop_pubkey(input: PublicKey) {
+
        assert_eq!(deserialize::<PublicKey>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_id(input: Id) {
+
        assert_eq!(deserialize::<Id>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_digest(input: Digest) {
+
        assert_eq!(deserialize::<Digest>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_refs(input: Refs) {
+
        assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
+
    }
+

+
    #[quickcheck]
+
    fn prop_signature(input: arbitrary::ByteArray<64>) {
+
        let signature = Signature::from(input.into_inner());
+

+
        assert_eq!(
+
            deserialize::<Signature>(&serialize(&signature)).unwrap(),
+
            signature
+
        );
+
    }
+

+
    #[quickcheck]
+
    fn prop_oid(input: arbitrary::ByteArray<20>) {
+
        let oid = git::Oid::try_from(input.into_inner().as_slice()).unwrap();
+

+
        assert_eq!(deserialize::<git::Oid>(&serialize(&oid)).unwrap(), oid);
+
    }
+

+
    #[quickcheck]
+
    fn prop_signed_refs(input: SignedRefs<Unverified>) {
+
        assert_eq!(
+
            deserialize::<SignedRefs<Unverified>>(&serialize(&input)).unwrap(),
+
            input
+
        );
+
    }
+

+
    #[test]
+
    fn test_string() {
+
        assert_eq!(
+
            serialize(&String::from("hello")),
+
            vec![5, b'h', b'e', b'l', b'l', b'o']
+
        );
+
    }
+

+
    #[test]
+
    fn test_git_url() {
+
        let url = git::Url {
+
            scheme: git::url::Scheme::Https,
+
            path: "/git".to_owned().into(),
+
            host: Some("seed.radicle.xyz".to_owned()),
+
            port: Some(8888),
+
            ..git::Url::default()
+
        };
+
        assert_eq!(deserialize::<git::Url>(&serialize(&url)).unwrap(), url);
+
    }
+
}
modified node/src/storage/refs.rs
@@ -15,7 +15,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::service::wire;
use crate::storage;
use crate::storage::{ReadRepository, RemoteId, WriteRepository};

modified node/src/test/arbitrary.rs
@@ -14,12 +14,12 @@ use crate::crypto::{PublicKey, SecretKey};
use crate::git;
use crate::hash;
use crate::identity::{Delegate, Did, Doc, Id, Project};
-
use crate::protocol::filter::{Filter, FILTER_SIZE};
-
use crate::protocol::message::{
+
use crate::service::filter::{Filter, FILTER_SIZE};
+
use crate::service::message::{
    Address, Envelope, InventoryAnnouncement, Message, MessageType, NodeAnnouncement,
    RefsAnnouncement, Subscribe,
};
-
use crate::protocol::{NodeId, Timestamp};
+
use crate::service::{NodeId, Timestamp};
use crate::storage;
use crate::storage::refs::{Refs, SignedRefs};
use crate::test::storage::MockStorage;
modified node/src/test/handle.rs
@@ -3,8 +3,8 @@ use std::sync::{Arc, Mutex};
use crate::client::handle::traits;
use crate::client::handle::Error;
use crate::identity::Id;
-
use crate::protocol;
-
use crate::protocol::FetchLookup;
+
use crate::service;
+
use crate::service::FetchLookup;

#[derive(Default, Clone)]
pub struct Handle {
@@ -30,7 +30,7 @@ impl traits::Handle for Handle {
        Ok(())
    }

-
    fn command(&self, _cmd: protocol::Command) -> Result<(), Error> {
+
    fn command(&self, _cmd: service::Command) -> Result<(), Error> {
        Ok(())
    }

modified node/src/test/peer.rs
@@ -7,22 +7,22 @@ use log::*;
use crate::address_book::{KnownAddress, Source};
use crate::clock::RefClock;
use crate::collections::HashMap;
-
use crate::protocol;
-
use crate::protocol::config::*;
-
use crate::protocol::message::*;
-
use crate::protocol::*;
+
use crate::service;
+
use crate::service::config::*;
+
use crate::service::message::*;
+
use crate::service::*;
use crate::storage::WriteStorage;
use crate::test::crypto::MockSigner;
use crate::test::simulator;
use crate::{Link, LocalTime};

-
/// Protocol instantiation used for testing.
-
pub type Protocol<S> = protocol::Protocol<HashMap<net::IpAddr, KnownAddress>, S, MockSigner>;
+
/// Service instantiation used for testing.
+
pub type Service<S> = service::Service<HashMap<net::IpAddr, KnownAddress>, S, MockSigner>;

#[derive(Debug)]
pub struct Peer<S> {
    pub name: &'static str,
-
    pub protocol: Protocol<S>,
+
    pub service: Service<S>,
    pub ip: net::IpAddr,
    pub rng: fastrand::Rng,
    pub local_time: LocalTime,
@@ -45,16 +45,16 @@ where
}

impl<S> Deref for Peer<S> {
-
    type Target = Protocol<S>;
+
    type Target = Service<S>;

    fn deref(&self) -> &Self::Target {
-
        &self.protocol
+
        &self.service
    }
}

impl<S> DerefMut for Peer<S> {
    fn deref_mut(&mut self) -> &mut Self::Target {
-
        &mut self.protocol
+
        &mut self.service
    }
}

@@ -91,13 +91,13 @@ where
        let local_time = LocalTime::now();
        let clock = RefClock::from(local_time);
        let signer = MockSigner::new(&mut rng);
-
        let protocol = Protocol::new(config, clock, storage, addrs, signer, rng.clone());
+
        let service = Service::new(config, clock, storage, addrs, signer, rng.clone());
        let ip = ip.into();
        let local_addr = net::SocketAddr::new(ip, rng.u16(..));

        Self {
            name,
-
            protocol,
+
            service,
            ip,
            local_addr,
            rng,
@@ -111,12 +111,12 @@ where
            info!("{}: Initializing: address = {}", self.name, self.ip);

            self.initialized = true;
-
            self.protocol.initialize(LocalTime::now());
+
            self.service.initialize(LocalTime::now());
        }
    }

    pub fn timestamp(&self) -> Timestamp {
-
        self.protocol.timestamp()
+
        self.service.timestamp()
    }

    pub fn git_url(&self) -> Url {
@@ -124,11 +124,11 @@ where
    }

    pub fn node_id(&self) -> NodeId {
-
        self.protocol.node_id()
+
        self.service.node_id()
    }

    pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) {
-
        self.protocol
+
        self.service
            .received_message(peer, self.config().network.envelope(msg));
    }

@@ -139,7 +139,7 @@ where
        let git = Url::from_bytes(git.as_bytes()).unwrap();

        self.initialize();
-
        self.protocol.connected(remote, &local, Link::Inbound);
+
        self.service.connected(remote, &local, Link::Inbound);
        self.receive(
            &remote,
            Message::init(
@@ -161,8 +161,8 @@ where
        let remote = simulator::Peer::<S>::addr(peer);

        self.initialize();
-
        self.protocol.attempted(&remote);
-
        self.protocol
+
        self.service.attempted(&remote);
+
        self.service
            .connected(remote, &self.local_addr, Link::Outbound);

        let mut msgs = self.messages(&remote);
@@ -187,8 +187,8 @@ where
    pub fn messages(&mut self, remote: &net::SocketAddr) -> impl Iterator<Item = Message> {
        let mut msgs = Vec::new();

-
        self.protocol.outbox().retain(|o| match o {
-
            protocol::Io::Write(a, envelopes) if a == remote => {
+
        self.service.outbox().retain(|o| match o {
+
            service::Io::Write(a, envelopes) if a == remote => {
                msgs.extend(envelopes.iter().map(|e| e.msg.clone()));
                false
            }
@@ -206,6 +206,6 @@ where

    /// Get a draining iterator over the peer's I/O outbox.
    pub fn outbox(&mut self) -> impl Iterator<Item = Io> + '_ {
-
        self.protocol.outbox().drain(..)
+
        self.service.outbox().drain(..)
    }
}
modified node/src/test/simulator.rs
@@ -13,9 +13,9 @@ use log::*;
use nakamoto_net as nakamoto;
use nakamoto_net::{Link, LocalDuration, LocalTime};

-
use crate::protocol::{DisconnectReason, Envelope, Event, Io};
+
use crate::service::{DisconnectReason, Envelope, Event, Io};
use crate::storage::WriteStorage;
-
use crate::test::peer::Protocol;
+
use crate::test::peer::Service;

/// Minimum latency between peers.
pub const MIN_LATENCY: LocalDuration = LocalDuration::from_millis(1);
@@ -26,16 +26,16 @@ pub const MAX_EVENTS: usize = 2048;
/// The simulator requires each peer to have a distinct IP address.
type NodeId = std::net::IpAddr;

-
/// A simulated peer. Protocol instances have to be wrapped in this type to be simulated.
-
pub trait Peer<S>: Deref<Target = Protocol<S>> + DerefMut<Target = Protocol<S>> + 'static {
-
    /// Initialize the peer. This should at minimum initialize the protocol with the
+
/// A simulated peer. Service instances have to be wrapped in this type to be simulated.
+
pub trait Peer<S>: Deref<Target = Service<S>> + DerefMut<Target = Service<S>> + 'static {
+
    /// Initialize the peer. This should at minimum initialize the service with the
    /// current time.
    fn init(&mut self);
    /// Get the peer address.
    fn addr(&self) -> net::SocketAddr;
}

-
/// Simulated protocol input.
+
/// Simulated service input.
#[derive(Debug, Clone)]
pub enum Input {
    /// Connection attempt underway.
@@ -63,7 +63,7 @@ pub enum Input {
    Wake,
}

-
/// A scheduled protocol input.
+
/// A scheduled service input.
#[derive(Debug, Clone)]
pub struct Scheduled {
    /// The node for which this input is scheduled.
@@ -349,8 +349,8 @@ impl<'r, S: WriteStorage<'r>> Simulation<S> {
                trace!(target: "sim", "{:05} {}", elapsed, next);
            } else {
                // TODO: This can be confusing, since this event may not actually be passed to
-
                // the protocol. It would be best to only log the events that are being sent
-
                // to the protocol, or to log when an input is being dropped.
+
                // the service. It would be best to only log the events that are being sent
+
                // to the service, or to log when an input is being dropped.
                info!(target: "sim", "{:05} {} ({})", elapsed, next, self.inbox.messages.len());
            }
            assert!(time >= self.time, "Time only moves forwards!");
@@ -415,7 +415,7 @@ impl<'r, S: WriteStorage<'r>> Simulation<S> {
        !self.is_done()
    }

-
    /// Process a protocol output event from a node.
+
    /// Process a service output event from a node.
    pub fn schedule(&mut self, node: &NodeId, out: Io) {
        let node = *node;

@@ -491,7 +491,7 @@ impl<'r, S: WriteStorage<'r>> Simulation<S> {
                if self.is_partitioned(node, remote.ip()) {
                    log::info!(target: "sim", "{} -/-> {} (partitioned)", node, remote.ip());

-
                    // Sometimes, the protocol gets a failure input, other times it just hangs.
+
                    // Sometimes, the service gets a failure input, other times it just hangs.
                    if self.rng.bool() {
                        self.inbox.insert(
                            self.time + MIN_LATENCY,
modified node/src/test/tests.rs
@@ -5,10 +5,10 @@ use crossbeam_channel as chan;
use nakamoto_net as nakamoto;

use crate::collections::{HashMap, HashSet};
-
use crate::protocol::config::*;
-
use crate::protocol::message::*;
-
use crate::protocol::peer::*;
-
use crate::protocol::*;
+
use crate::service::config::*;
+
use crate::service::message::*;
+
use crate::service::peer::*;
+
use crate::service::*;
use crate::storage::git::Storage;
use crate::storage::ReadStorage;
use crate::test::fixtures;
@@ -19,7 +19,7 @@ use crate::test::simulator;
use crate::test::simulator::{Peer as _, Simulation};
use crate::test::storage::MockStorage;
use crate::{assert_matches, Link, LocalTime};
-
use crate::{client, identity, protocol, rad, storage, test};
+
use crate::{client, identity, rad, service, storage, test};

// NOTE
//
@@ -39,7 +39,7 @@ fn test_outbound_connection() {
    alice.connect_to(&eve);

    let peers = alice
-
        .protocol
+
        .service
        .peers()
        .negotiated()
        .map(|(ip, _)| *ip)
@@ -59,7 +59,7 @@ fn test_inbound_connection() {
    alice.connect_from(&eve);

    let peers = alice
-
        .protocol
+
        .service
        .peers()
        .negotiated()
        .map(|(ip, _)| *ip)
@@ -396,8 +396,8 @@ fn test_push_and_pull() {
    let mut eve = Peer::new("eve", [9, 9, 9, 9], storage_eve);

    // Alice and Bob connect to Eve.
-
    alice.command(protocol::Command::Connect(eve.addr()));
-
    bob.command(protocol::Command::Connect(eve.addr()));
+
    alice.command(service::Command::Connect(eve.addr()));
+
    bob.command(service::Command::Connect(eve.addr()));

    let mut sim = Simulation::new(
        LocalTime::now(),
@@ -422,11 +422,11 @@ fn test_push_and_pull() {

    // Bob tracks Alice's project.
    let (sender, _) = chan::bounded(1);
-
    bob.command(protocol::Command::Track(proj_id.clone(), sender));
+
    bob.command(service::Command::Track(proj_id.clone(), sender));

    // Eve tracks Alice's project.
    let (sender, _) = chan::bounded(1);
-
    eve.command(protocol::Command::Track(proj_id.clone(), sender));
+
    eve.command(service::Command::Track(proj_id.clone(), sender));

    // Neither of them have it in the beginning.
    assert!(eve.get(&proj_id).unwrap().is_none());
@@ -435,7 +435,7 @@ fn test_push_and_pull() {
    // Alice announces her refs.
    // We now expect Eve to fetch Alice's project from Alice.
    // Then we expect Bob to fetch Alice's project from Eve.
-
    alice.command(protocol::Command::AnnounceRefs(proj_id.clone()));
+
    alice.command(service::Command::AnnounceRefs(proj_id.clone()));
    sim.run_while([&mut alice, &mut bob, &mut eve], |s| !s.is_settled());

    assert!(eve
@@ -450,7 +450,7 @@ fn test_push_and_pull() {
        .is_some());
    assert_matches!(
        sim.events(&bob.ip).next(),
-
        Some(protocol::Event::RefsFetched { from, .. })
+
        Some(service::Event::RefsFetched { from, .. })
        if from == eve.git_url(),
        "Bob fetched from Eve"
    );
modified node/src/transport.rs
@@ -8,8 +8,8 @@ use nakamoto_net::{Io, Link};
use crate::address_book;
use crate::collections::HashMap;
use crate::crypto;
-
use crate::protocol::wire::Wire;
-
use crate::protocol::{Command, DisconnectReason, Event, Protocol};
+
use crate::service::wire::Wire;
+
use crate::service::{Command, DisconnectReason, Event, Service};
use crate::storage::WriteStorage;

#[derive(Debug)]
@@ -93,7 +93,7 @@ impl<S, T, G> Iterator for Transport<S, T, G> {
}

impl<S, T, G> Deref for Transport<S, T, G> {
-
    type Target = Protocol<S, T, G>;
+
    type Target = Service<S, T, G>;

    fn deref(&self) -> &Self::Target {
        &self.inner