Refactor service
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
7169881849
commit
fcb1007f9d
|
|
@ -3,6 +3,9 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{LocalDuration, LocalTime};
|
use crate::{LocalDuration, LocalTime};
|
||||||
|
|
||||||
|
/// Seconds since epoch.
|
||||||
|
pub type Timestamp = u64;
|
||||||
|
|
||||||
/// Clock with interior mutability.
|
/// Clock with interior mutability.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RefClock(Rc<RefCell<LocalTime>>);
|
pub struct RefClock(Rc<RefCell<LocalTime>>);
|
||||||
|
|
@ -28,6 +31,10 @@ impl RefClock {
|
||||||
pub fn set(&mut self, time: LocalTime) {
|
pub fn set(&mut self, time: LocalTime) {
|
||||||
*self.borrow_mut() = time;
|
*self.borrow_mut() = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(&self) -> Timestamp {
|
||||||
|
self.local_time().as_secs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LocalTime> for RefClock {
|
impl From<LocalTime> for RefClock {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
pub use nakamoto_net::{Io, Link, LocalDuration, LocalTime};
|
|
||||||
|
|
||||||
pub mod address_book;
|
pub mod address_book;
|
||||||
pub mod address_manager;
|
pub mod address_manager;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
@ -14,15 +12,18 @@ pub mod test;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
pub mod wire;
|
pub mod wire;
|
||||||
|
|
||||||
|
pub use nakamoto_net::{Io, Link, LocalDuration, LocalTime};
|
||||||
pub use radicle::{collections, crypto, git, hash, identity, rad, storage};
|
pub use radicle::{collections, crypto, git, hash, identity, rad, storage};
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use crate::clock::Timestamp;
|
||||||
pub use crate::crypto::{PublicKey, Signature, Signer};
|
pub use crate::crypto::{PublicKey, Signature, Signer};
|
||||||
pub use crate::decoder::Decoder;
|
pub use crate::decoder::Decoder;
|
||||||
pub use crate::hash::Digest;
|
pub use crate::hash::Digest;
|
||||||
pub use crate::identity::{Did, Id};
|
pub use crate::identity::{Did, Id};
|
||||||
pub use crate::service::filter::Filter;
|
pub use crate::service::filter::Filter;
|
||||||
pub use crate::service::{NodeId, Timestamp};
|
pub use crate::service::{DisconnectReason, Envelope, Event, Message, Network, NodeId};
|
||||||
pub use crate::storage::refs::Refs;
|
pub use crate::storage::refs::Refs;
|
||||||
pub use crate::storage::WriteStorage;
|
pub use crate::storage::WriteStorage;
|
||||||
|
pub use crate::{LocalDuration, LocalTime};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod peer;
|
pub mod peer;
|
||||||
|
pub mod reactor;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::BTreeMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::{fmt, net, net::IpAddr};
|
use std::{fmt, net, net::IpAddr};
|
||||||
|
|
||||||
|
|
@ -15,14 +15,15 @@ use nakamoto::{LocalDuration, LocalTime};
|
||||||
use nakamoto_net as nakamoto;
|
use nakamoto_net as nakamoto;
|
||||||
use nakamoto_net::Link;
|
use nakamoto_net::Link;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
|
use radicle::storage::ReadStorage;
|
||||||
|
|
||||||
use crate::address_book;
|
use crate::address_book;
|
||||||
use crate::address_book::AddressBook;
|
use crate::address_book::AddressBook;
|
||||||
use crate::address_manager::AddressManager;
|
use crate::address_manager::AddressManager;
|
||||||
use crate::clock::RefClock;
|
use crate::clock::{RefClock, Timestamp};
|
||||||
use crate::collections::{HashMap, HashSet};
|
use crate::collections::{HashMap, HashSet};
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use crate::crypto::Verified;
|
use crate::crypto::{Signer, Verified};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::git::Url;
|
use crate::git::Url;
|
||||||
use crate::identity::{Doc, Id};
|
use crate::identity::{Doc, Id};
|
||||||
|
|
@ -35,8 +36,8 @@ use crate::storage::{Inventory, ReadRepository, RefUpdate, WriteRepository, Writ
|
||||||
pub use crate::service::config::{Config, Network};
|
pub use crate::service::config::{Config, Network};
|
||||||
pub use crate::service::message::{Envelope, Message};
|
pub use crate::service::message::{Envelope, Message};
|
||||||
|
|
||||||
use self::filter::Filter;
|
|
||||||
use self::message::{InventoryAnnouncement, NodeFeatures};
|
use self::message::{InventoryAnnouncement, NodeFeatures};
|
||||||
|
use self::reactor::Reactor;
|
||||||
|
|
||||||
pub const DEFAULT_PORT: u16 = 8776;
|
pub const DEFAULT_PORT: u16 = 8776;
|
||||||
pub const PROTOCOL_VERSION: u32 = 1;
|
pub const PROTOCOL_VERSION: u32 = 1;
|
||||||
|
|
@ -52,23 +53,6 @@ pub const MAX_TIME_DELTA: LocalDuration = LocalDuration::from_mins(60);
|
||||||
pub type NodeId = crypto::PublicKey;
|
pub type NodeId = crypto::PublicKey;
|
||||||
/// Network routing table. Keeps track of where projects are hosted.
|
/// Network routing table. Keeps track of where projects are hosted.
|
||||||
pub type Routing = HashMap<Id, HashSet<NodeId>>;
|
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.
|
/// A service event.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -138,11 +122,27 @@ pub enum Command {
|
||||||
pub enum CommandError {}
|
pub enum CommandError {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Service<S, T, G> {
|
pub struct Service<A, S, G> {
|
||||||
|
/// Service configuration.
|
||||||
|
config: Config,
|
||||||
|
/// Our cryptographic signer and key.
|
||||||
|
signer: G,
|
||||||
|
/// Project storage.
|
||||||
|
storage: S,
|
||||||
|
/// Tracks the location of projects.
|
||||||
|
routing: Routing,
|
||||||
/// Peer sessions, currently or recently connected.
|
/// Peer sessions, currently or recently connected.
|
||||||
sessions: Sessions,
|
sessions: Sessions,
|
||||||
/// Service state that isn't peer-specific.
|
/// Keeps track of peer states.
|
||||||
context: Context<S, T, G>,
|
peers: BTreeMap<NodeId, Peer>,
|
||||||
|
/// Clock. Tells the time.
|
||||||
|
clock: RefClock,
|
||||||
|
/// Interface to the I/O reactor.
|
||||||
|
reactor: Reactor,
|
||||||
|
/// Peer address manager.
|
||||||
|
addrmgr: AddressManager<A>,
|
||||||
|
/// Source of entropy.
|
||||||
|
rng: Rng,
|
||||||
/// Whether our local inventory no long represents what we have announced to the network.
|
/// Whether our local inventory no long represents what we have announced to the network.
|
||||||
out_of_sync: bool,
|
out_of_sync: bool,
|
||||||
/// Last time the service was idle.
|
/// Last time the service was idle.
|
||||||
|
|
@ -157,20 +157,36 @@ pub struct Service<S, T, G> {
|
||||||
start_time: LocalTime,
|
start_time: LocalTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service<S, T, G> {
|
impl<A, S, G> Service<A, S, G>
|
||||||
|
where
|
||||||
|
A: address_book::Store,
|
||||||
|
S: WriteStorage + 'static,
|
||||||
|
G: crypto::Signer,
|
||||||
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: Config,
|
config: Config,
|
||||||
clock: RefClock,
|
clock: RefClock,
|
||||||
storage: T,
|
storage: S,
|
||||||
addresses: S,
|
addresses: A,
|
||||||
signer: G,
|
signer: G,
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let addrmgr = AddressManager::new(addresses);
|
let addrmgr = AddressManager::new(addresses);
|
||||||
|
let routing = HashMap::with_hasher(rng.clone().into());
|
||||||
|
let sessions = Sessions::new(rng.clone());
|
||||||
|
let network = config.network;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
context: Context::new(config, clock, storage, addrmgr, signer, rng.clone()),
|
config,
|
||||||
sessions: Sessions::new(rng),
|
storage,
|
||||||
|
addrmgr,
|
||||||
|
signer,
|
||||||
|
rng,
|
||||||
|
clock,
|
||||||
|
routing,
|
||||||
|
peers: BTreeMap::new(),
|
||||||
|
reactor: Reactor::new(network),
|
||||||
|
sessions,
|
||||||
out_of_sync: false,
|
out_of_sync: false,
|
||||||
last_idle: LocalTime::default(),
|
last_idle: LocalTime::default(),
|
||||||
last_sync: LocalTime::default(),
|
last_sync: LocalTime::default(),
|
||||||
|
|
@ -180,10 +196,8 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnect(&mut self, remote: &IpAddr, reason: DisconnectReason) {
|
pub fn node_id(&self) -> NodeId {
|
||||||
if let Some(addr) = self.sessions.get(remote).map(|p| p.addr) {
|
*self.signer.public_key()
|
||||||
self.context.disconnect(addr, reason);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seeds(&self, id: &Id) -> Box<dyn Iterator<Item = (&NodeId, &Session)> + '_> {
|
pub fn seeds(&self, id: &Id) -> Box<dyn Iterator<Item = (&NodeId, &Session)> + '_> {
|
||||||
|
|
@ -243,17 +257,17 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
|
|
||||||
/// Get the current inventory.
|
/// Get the current inventory.
|
||||||
pub fn inventory(&self) -> Result<Inventory, storage::Error> {
|
pub fn inventory(&self) -> Result<Inventory, storage::Error> {
|
||||||
self.context.storage.inventory()
|
self.storage.inventory()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the storage instance.
|
/// Get the storage instance.
|
||||||
pub fn storage(&self) -> &T {
|
pub fn storage(&self) -> &S {
|
||||||
&self.context.storage
|
&self.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the mutable storage instance.
|
/// Get the mutable storage instance.
|
||||||
pub fn storage_mut(&mut self) -> &mut T {
|
pub fn storage_mut(&mut self) -> &mut S {
|
||||||
&mut self.context.storage
|
&mut self.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a project from storage, using the local node's key.
|
/// Get a project from storage, using the local node's key.
|
||||||
|
|
@ -263,34 +277,38 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
|
|
||||||
/// Get the local signer.
|
/// Get the local signer.
|
||||||
pub fn signer(&self) -> &G {
|
pub fn signer(&self) -> &G {
|
||||||
&self.context.signer
|
&self.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the clock.
|
||||||
|
pub fn clock(&self) -> &RefClock {
|
||||||
|
&self.clock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the local service time.
|
/// Get the local service time.
|
||||||
pub fn local_time(&self) -> LocalTime {
|
pub fn local_time(&self) -> LocalTime {
|
||||||
self.context.clock.local_time()
|
self.clock.local_time()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get service configuration.
|
/// Get service configuration.
|
||||||
pub fn config(&self) -> &Config {
|
pub fn config(&self) -> &Config {
|
||||||
&self.context.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to routing table.
|
/// Get reference to routing table.
|
||||||
pub fn routing(&self) -> &Routing {
|
pub fn routing(&self) -> &Routing {
|
||||||
&self.context.routing
|
&self.routing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get I/O outbox.
|
/// Get I/O reactor.
|
||||||
pub fn outbox(&mut self) -> &mut VecDeque<Io> {
|
pub fn reactor(&mut self) -> &mut Reactor {
|
||||||
&mut self.context.io
|
&mut self.reactor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup(&self, id: &Id) -> Lookup {
|
pub fn lookup(&self, id: &Id) -> Lookup {
|
||||||
Lookup {
|
Lookup {
|
||||||
local: self.context.storage.get(&self.node_id(), id).unwrap(),
|
local: self.storage.get(&self.node_id(), id).unwrap(),
|
||||||
remote: self
|
remote: self
|
||||||
.context
|
|
||||||
.routing
|
.routing
|
||||||
.get(id)
|
.get(id)
|
||||||
.map_or(vec![], |r| r.iter().cloned().collect()),
|
.map_or(vec![], |r| r.iter().cloned().collect()),
|
||||||
|
|
@ -303,20 +321,20 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
self.start_time = time;
|
self.start_time = time;
|
||||||
|
|
||||||
// Connect to configured peers.
|
// Connect to configured peers.
|
||||||
let addrs = self.context.config.connect.clone();
|
let addrs = self.config.connect.clone();
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
self.context.connect(addr);
|
self.reactor.connect(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, now: nakamoto::LocalTime) {
|
pub fn tick(&mut self, now: nakamoto::LocalTime) {
|
||||||
trace!("Tick +{}", now - self.start_time);
|
trace!("Tick +{}", now - self.start_time);
|
||||||
|
|
||||||
self.context.clock.set(now);
|
self.clock.set(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wake(&mut self) {
|
pub fn wake(&mut self) {
|
||||||
let now = self.context.clock.local_time();
|
let now = self.clock.local_time();
|
||||||
|
|
||||||
trace!("Wake +{}", now - self.start_time);
|
trace!("Wake +{}", now - self.start_time);
|
||||||
|
|
||||||
|
|
@ -324,28 +342,28 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
debug!("Running 'idle' task...");
|
debug!("Running 'idle' task...");
|
||||||
|
|
||||||
self.maintain_connections();
|
self.maintain_connections();
|
||||||
self.context.io.push_back(Io::Wakeup(IDLE_INTERVAL));
|
self.reactor.wakeup(IDLE_INTERVAL);
|
||||||
self.last_idle = now;
|
self.last_idle = now;
|
||||||
}
|
}
|
||||||
if now - self.last_sync >= SYNC_INTERVAL {
|
if now - self.last_sync >= SYNC_INTERVAL {
|
||||||
debug!("Running 'sync' task...");
|
debug!("Running 'sync' task...");
|
||||||
|
|
||||||
// TODO: What do we do here?
|
// TODO: What do we do here?
|
||||||
self.context.io.push_back(Io::Wakeup(SYNC_INTERVAL));
|
self.reactor.wakeup(SYNC_INTERVAL);
|
||||||
self.last_sync = now;
|
self.last_sync = now;
|
||||||
}
|
}
|
||||||
if now - self.last_announce >= ANNOUNCE_INTERVAL {
|
if now - self.last_announce >= ANNOUNCE_INTERVAL {
|
||||||
if self.out_of_sync {
|
if self.out_of_sync {
|
||||||
self.announce_inventory().unwrap();
|
self.announce_inventory().unwrap();
|
||||||
}
|
}
|
||||||
self.context.io.push_back(Io::Wakeup(ANNOUNCE_INTERVAL));
|
self.reactor.wakeup(ANNOUNCE_INTERVAL);
|
||||||
self.last_announce = now;
|
self.last_announce = now;
|
||||||
}
|
}
|
||||||
if now - self.last_prune >= PRUNE_INTERVAL {
|
if now - self.last_prune >= PRUNE_INTERVAL {
|
||||||
debug!("Running 'prune' task...");
|
debug!("Running 'prune' task...");
|
||||||
|
|
||||||
self.prune_routing_entries();
|
self.prune_routing_entries();
|
||||||
self.context.io.push_back(Io::Wakeup(PRUNE_INTERVAL));
|
self.reactor.wakeup(PRUNE_INTERVAL);
|
||||||
self.last_prune = now;
|
self.last_prune = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +372,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
debug!("Command {:?}", cmd);
|
debug!("Command {:?}", cmd);
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
Command::Connect(addr) => self.context.connect(addr),
|
Command::Connect(addr) => self.reactor.connect(addr),
|
||||||
Command::Fetch(id, resp) => {
|
Command::Fetch(id, resp) => {
|
||||||
if !self.config.is_tracking(&id) {
|
if !self.config.is_tracking(&id) {
|
||||||
resp.send(FetchLookup::NotTracking).ok();
|
resp.send(FetchLookup::NotTracking).ok();
|
||||||
|
|
@ -433,7 +451,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
let message = RefsAnnouncement { id, refs };
|
let message = RefsAnnouncement { id, refs };
|
||||||
let signature = message.sign(&self.signer);
|
let signature = message.sign(&self.signer);
|
||||||
|
|
||||||
self.context.broadcast(
|
self.reactor.broadcast(
|
||||||
Message::RefsAnnouncement {
|
Message::RefsAnnouncement {
|
||||||
node,
|
node,
|
||||||
message,
|
message,
|
||||||
|
|
@ -447,7 +465,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
|
|
||||||
pub fn attempted(&mut self, addr: &std::net::SocketAddr) {
|
pub fn attempted(&mut self, addr: &std::net::SocketAddr) {
|
||||||
let ip = addr.ip();
|
let ip = addr.ip();
|
||||||
let persistent = self.context.config.is_persistent(addr);
|
let persistent = self.config.is_persistent(addr);
|
||||||
let peer = self
|
let peer = self
|
||||||
.sessions
|
.sessions
|
||||||
.entry(ip)
|
.entry(ip)
|
||||||
|
|
@ -470,21 +488,24 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
// For inbound connections, we wait for the remote to say "Hello" first.
|
// 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?
|
// TODO: How should we deal with multiple peers connecting from the same IP address?
|
||||||
if link.is_outbound() {
|
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.sessions.get_mut(&ip) {
|
if let Some(peer) = self.sessions.get_mut(&ip) {
|
||||||
self.context.write_all(peer.addr, messages);
|
if link.is_outbound() {
|
||||||
peer.connected();
|
self.reactor.write_all(
|
||||||
|
addr,
|
||||||
|
gossip::handshake(
|
||||||
|
self.clock.timestamp(),
|
||||||
|
&self.storage,
|
||||||
|
&self.signer,
|
||||||
|
&self.config,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
peer.connected(link);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.sessions.insert(
|
self.sessions.insert(
|
||||||
ip,
|
ip,
|
||||||
Session::new(
|
Session::new(addr, Link::Inbound, self.config.is_persistent(&addr)),
|
||||||
addr,
|
|
||||||
Link::Inbound,
|
|
||||||
self.context.config.is_persistent(&addr),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,8 +524,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
peer.state = SessionState::Disconnected { since };
|
peer.state = SessionState::Disconnected { since };
|
||||||
|
|
||||||
// Attempt to re-connect to persistent peers.
|
// Attempt to re-connect to persistent peers.
|
||||||
if self.context.config.is_persistent(addr) && peer.attempts() < MAX_CONNECTION_ATTEMPTS
|
if self.config.is_persistent(addr) && peer.attempts() < MAX_CONNECTION_ATTEMPTS {
|
||||||
{
|
|
||||||
if reason.is_dial_err() {
|
if reason.is_dial_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +540,7 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
// TODO: Try to reconnect only if the peer was attempted. A disconnect without
|
// 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.
|
// even a successful attempt means that we're unlikely to be able to reconnect.
|
||||||
|
|
||||||
self.context.connect(*addr);
|
self.reactor.connect(*addr);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Non-persistent peers should be removed from the
|
// TODO: Non-persistent peers should be removed from the
|
||||||
// map here or at some later point.
|
// map here or at some later point.
|
||||||
|
|
@ -528,36 +548,208 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn received_message(&mut self, addr: &std::net::SocketAddr, msg: Envelope) {
|
pub fn received_message(&mut self, addr: &net::SocketAddr, envelope: Envelope) {
|
||||||
let peer_ip = addr.ip();
|
match self.handle_message(addr, envelope) {
|
||||||
|
Ok(relay) => {
|
||||||
|
if let Some(msg) = relay {
|
||||||
|
let negotiated = self
|
||||||
|
.sessions
|
||||||
|
.negotiated()
|
||||||
|
.filter(|(ip, _)| **ip != addr.ip())
|
||||||
|
.map(|(_, p)| p);
|
||||||
|
|
||||||
|
self.reactor.relay(msg, negotiated.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(SessionError::NotFound(ip)) => {
|
||||||
|
error!("Session not found for {}", ip);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// If there's an error, stop processing messages from this peer.
|
||||||
|
// However, we still relay messages returned up to this point.
|
||||||
|
self.reactor.disconnect(*addr, DisconnectReason::Error(err));
|
||||||
|
|
||||||
|
// FIXME: The peer should be set in a state such that we don'that
|
||||||
|
// process further messages.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_message(
|
||||||
|
&mut self,
|
||||||
|
remote: &net::SocketAddr,
|
||||||
|
envelope: Envelope,
|
||||||
|
) -> Result<Option<Message>, peer::SessionError> {
|
||||||
|
let peer_ip = remote.ip();
|
||||||
let peer = if let Some(peer) = self.sessions.get_mut(&peer_ip) {
|
let peer = if let Some(peer) = self.sessions.get_mut(&peer_ip) {
|
||||||
peer
|
peer
|
||||||
} else {
|
} else {
|
||||||
return;
|
return Err(SessionError::NotFound(remote.ip()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let relay = match peer.received(msg, &mut self.context) {
|
if envelope.magic != self.config.network.magic() {
|
||||||
Ok(msg) => msg,
|
return Err(SessionError::WrongMagic(envelope.magic));
|
||||||
Err(err) => {
|
}
|
||||||
self.context
|
debug!("Received {:?} from {}", &envelope.msg, peer.ip());
|
||||||
.disconnect(peer.addr, DisconnectReason::Error(err));
|
|
||||||
// If there's an error, stop processing messages from this peer.
|
match (&peer.state, envelope.msg) {
|
||||||
// However, we still relay messages returned up to this point.
|
(
|
||||||
//
|
SessionState::Initial,
|
||||||
// FIXME: The peer should be set in a state such that we don'that
|
Message::Initialize {
|
||||||
// process further messages.
|
id,
|
||||||
return;
|
version,
|
||||||
|
addrs,
|
||||||
|
git,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
if version != PROTOCOL_VERSION {
|
||||||
|
return Err(SessionError::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 peer.link.is_inbound() {
|
||||||
|
self.reactor.write_all(
|
||||||
|
peer.addr,
|
||||||
|
gossip::handshake(
|
||||||
|
self.clock.timestamp(),
|
||||||
|
&self.storage,
|
||||||
|
&self.signer,
|
||||||
|
&self.config,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 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.
|
||||||
|
peer.state = SessionState::Negotiated {
|
||||||
|
id,
|
||||||
|
since: self.clock.local_time(),
|
||||||
|
addrs,
|
||||||
|
git,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
(SessionState::Initial, _) => {
|
||||||
|
debug!(
|
||||||
|
"Disconnecting peer {} for sending us a message before handshake",
|
||||||
|
peer.ip()
|
||||||
|
);
|
||||||
|
return Err(SessionError::Misbehavior);
|
||||||
|
}
|
||||||
|
(
|
||||||
|
SessionState::Negotiated { git, .. },
|
||||||
|
Message::InventoryAnnouncement {
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let now = self.clock.local_time();
|
||||||
|
let peer = self.peers.entry(node).or_insert_with(Peer::default);
|
||||||
|
let relay = self.config.relay;
|
||||||
|
let git = git.clone();
|
||||||
|
|
||||||
if let Some(msg) = relay {
|
// Don't allow messages from too far in the future.
|
||||||
let negotiated = self
|
if message.timestamp.saturating_sub(now.as_secs()) > MAX_TIME_DELTA.as_secs() {
|
||||||
.sessions
|
return Err(SessionError::InvalidTimestamp(message.timestamp));
|
||||||
.negotiated()
|
}
|
||||||
.filter(|(ip, _)| **ip != peer_ip)
|
// Discard inventory messages we've already seen, otherwise update
|
||||||
.map(|(_, p)| p);
|
// out last seen time.
|
||||||
|
if message.timestamp > peer.last_message {
|
||||||
|
peer.last_message = message.timestamp;
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.process_inventory(&message.inventory, node, &git);
|
||||||
|
|
||||||
self.context.relay(msg, negotiated.clone());
|
if relay {
|
||||||
|
return Ok(Some(Message::InventoryAnnouncement {
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process a peer inventory update announcement by (maybe) fetching.
|
||||||
|
(
|
||||||
|
SessionState::Negotiated { git, .. },
|
||||||
|
Message::RefsAnnouncement {
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
// FIXME: Check message timestamp.
|
||||||
|
|
||||||
|
if message.verify(&node, &signature) {
|
||||||
|
// TODO: Buffer/throttle fetches.
|
||||||
|
// TODO: Check that we're tracking this user as well.
|
||||||
|
if self.config.is_tracking(&message.id) {
|
||||||
|
// TODO: Check refs to see if we should try to fetch or not.
|
||||||
|
let updated = self.storage.fetch(&message.id, git).unwrap();
|
||||||
|
let is_updated = !updated.is_empty();
|
||||||
|
|
||||||
|
self.reactor.event(Event::RefsFetched {
|
||||||
|
from: git.clone(),
|
||||||
|
project: message.id.clone(),
|
||||||
|
updated,
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_updated {
|
||||||
|
return Ok(Some(Message::RefsAnnouncement {
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(SessionError::Misbehavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
SessionState::Negotiated { .. },
|
||||||
|
Message::NodeAnnouncement {
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
// FIXME: Check message timestamp.
|
||||||
|
|
||||||
|
if !message.verify(&node, &signature) {
|
||||||
|
return Err(SessionError::Misbehavior);
|
||||||
|
}
|
||||||
|
log::warn!("Node announcement handling is not implemented");
|
||||||
|
}
|
||||||
|
(SessionState::Negotiated { .. }, Message::Subscribe(subscribe)) => {
|
||||||
|
peer.subscribe = Some(subscribe);
|
||||||
|
}
|
||||||
|
(SessionState::Negotiated { .. }, Message::Initialize { .. }) => {
|
||||||
|
debug!(
|
||||||
|
"Disconnecting peer {} for sending us a redundant handshake message",
|
||||||
|
peer.ip()
|
||||||
|
);
|
||||||
|
return Err(SessionError::Misbehavior);
|
||||||
|
}
|
||||||
|
(SessionState::Disconnected { .. }, msg) => {
|
||||||
|
debug!("Ignoring {:?} from disconnected peer {}", msg, peer.ip());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.storage.fetch(proj_id, remote).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,10 +759,14 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
|
|
||||||
/// Announce our inventory to all connected peers.
|
/// Announce our inventory to all connected peers.
|
||||||
fn announce_inventory(&mut self) -> Result<(), storage::Error> {
|
fn announce_inventory(&mut self) -> Result<(), storage::Error> {
|
||||||
let inv = Message::inventory(self.context.inventory_announcement()?, &self.context.signer);
|
let inventory = self.storage().inventory()?;
|
||||||
|
let inv = Message::inventory(
|
||||||
|
gossip::inventory(self.clock.timestamp(), inventory),
|
||||||
|
&self.signer,
|
||||||
|
);
|
||||||
|
|
||||||
for addr in self.sessions.negotiated().map(|(_, p)| p.addr) {
|
for addr in self.sessions.negotiated().map(|(_, p)| p.addr) {
|
||||||
self.context.write(addr, inv.clone());
|
self.reactor.write(addr, inv.clone());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -591,20 +787,6 @@ impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DisconnectReason {
|
pub enum DisconnectReason {
|
||||||
User,
|
User,
|
||||||
|
|
@ -635,11 +817,11 @@ impl fmt::Display for DisconnectReason {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, T, G> Iterator for Service<S, T, G> {
|
impl<A, S, G> Iterator for Service<A, S, G> {
|
||||||
type Item = Io;
|
type Item = reactor::Io;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.context.io.pop_front()
|
self.reactor.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,202 +841,6 @@ pub struct Peer {
|
||||||
pub last_message: Timestamp,
|
pub last_message: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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,
|
|
||||||
/// Keeps track of peer states.
|
|
||||||
peers: BTreeMap<NodeId, Peer>,
|
|
||||||
/// 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()),
|
|
||||||
peers: BTreeMap::new(),
|
|
||||||
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.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 Session>) {
|
|
||||||
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 Session>) {
|
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
/// Holds currently (or recently) connected peers.
|
/// Holds currently (or recently) connected peers.
|
||||||
pub struct Sessions(AddressBook<IpAddr, Session>);
|
pub struct Sessions(AddressBook<IpAddr, Session>);
|
||||||
|
|
@ -893,3 +879,44 @@ impl DerefMut for Sessions {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod gossip {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn handshake<G: Signer, S: ReadStorage>(
|
||||||
|
timestamp: Timestamp,
|
||||||
|
storage: &S,
|
||||||
|
signer: &G,
|
||||||
|
config: &Config,
|
||||||
|
) -> [Message; 4] {
|
||||||
|
let git = config.git_url.clone();
|
||||||
|
let inventory = storage.inventory().unwrap();
|
||||||
|
|
||||||
|
[
|
||||||
|
Message::init(*signer.public_key(), config.listen.clone(), git),
|
||||||
|
Message::node(gossip::node(timestamp, config), signer),
|
||||||
|
Message::inventory(gossip::inventory(timestamp, inventory), signer),
|
||||||
|
Message::subscribe(config.filter(), timestamp, Timestamp::MAX),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node(timestamp: Timestamp, config: &Config) -> NodeAnnouncement {
|
||||||
|
let features = NodeFeatures::default();
|
||||||
|
let alias = config.alias();
|
||||||
|
let addresses = vec![]; // TODO
|
||||||
|
|
||||||
|
NodeAnnouncement {
|
||||||
|
features,
|
||||||
|
timestamp,
|
||||||
|
alias,
|
||||||
|
addresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inventory(timestamp: Timestamp, inventory: Vec<Id>) -> InventoryAnnouncement {
|
||||||
|
InventoryAnnouncement {
|
||||||
|
inventory,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::collections::HashSet;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::git::Url;
|
use crate::git::Url;
|
||||||
use crate::identity::{Id, PublicKey};
|
use crate::identity::{Id, PublicKey};
|
||||||
|
use crate::service::filter::Filter;
|
||||||
use crate::service::message::{Address, Envelope, Message};
|
use crate::service::message::{Address, Envelope, Message};
|
||||||
|
|
||||||
/// Peer-to-peer network.
|
/// Peer-to-peer network.
|
||||||
|
|
@ -124,4 +125,18 @@ impl Config {
|
||||||
ProjectTracking::Allowed(ids) => ids.remove(&id),
|
ProjectTracking::Allowed(ids) => ids.remove(&id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn filter(&self) -> Filter {
|
||||||
|
match &self.project_tracking {
|
||||||
|
ProjectTracking::All { .. } => Filter::default(),
|
||||||
|
ProjectTracking::Allowed(ids) => Filter::new(ids.iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alias(&self) -> [u8; 32] {
|
||||||
|
let mut alias = [0u8; 32];
|
||||||
|
|
||||||
|
alias[..9].copy_from_slice("anonymous".as_bytes());
|
||||||
|
alias
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ pub enum SessionError {
|
||||||
WrongVersion(u32),
|
WrongVersion(u32),
|
||||||
#[error("invalid announcement timestamp: {0}")]
|
#[error("invalid announcement timestamp: {0}")]
|
||||||
InvalidTimestamp(u64),
|
InvalidTimestamp(u64),
|
||||||
|
#[error("session not found for address `{0}`")]
|
||||||
|
NotFound(net::IpAddr),
|
||||||
#[error("peer misbehaved")]
|
#[error("peer misbehaved")]
|
||||||
Misbehavior,
|
Misbehavior,
|
||||||
}
|
}
|
||||||
|
|
@ -84,157 +86,7 @@ impl Session {
|
||||||
self.attempts += 1;
|
self.attempts += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connected(&mut self) {
|
pub fn connected(&mut self, _link: Link) {
|
||||||
self.attempts = 0;
|
self.attempts = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn received<'r, S, T, G>(
|
|
||||||
&mut self,
|
|
||||||
envelope: Envelope,
|
|
||||||
ctx: &mut Context<S, T, G>,
|
|
||||||
) -> Result<Option<Message>, SessionError>
|
|
||||||
where
|
|
||||||
T: storage::WriteStorage<'r>,
|
|
||||||
G: crypto::Signer,
|
|
||||||
{
|
|
||||||
if envelope.magic != ctx.config.network.magic() {
|
|
||||||
return Err(SessionError::WrongMagic(envelope.magic));
|
|
||||||
}
|
|
||||||
debug!("Received {:?} from {}", &envelope.msg, self.ip());
|
|
||||||
|
|
||||||
match (&self.state, envelope.msg) {
|
|
||||||
(
|
|
||||||
SessionState::Initial,
|
|
||||||
Message::Initialize {
|
|
||||||
id,
|
|
||||||
version,
|
|
||||||
addrs,
|
|
||||||
git,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if version != PROTOCOL_VERSION {
|
|
||||||
return Err(SessionError::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 = SessionState::Negotiated {
|
|
||||||
id,
|
|
||||||
since: ctx.clock.local_time(),
|
|
||||||
addrs,
|
|
||||||
git,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(SessionState::Initial, _) => {
|
|
||||||
debug!(
|
|
||||||
"Disconnecting peer {} for sending us a message before handshake",
|
|
||||||
self.ip()
|
|
||||||
);
|
|
||||||
return Err(SessionError::Misbehavior);
|
|
||||||
}
|
|
||||||
(
|
|
||||||
SessionState::Negotiated { git, .. },
|
|
||||||
Message::InventoryAnnouncement {
|
|
||||||
node,
|
|
||||||
message,
|
|
||||||
signature,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
let now = ctx.clock.local_time();
|
|
||||||
let peer = ctx.peers.entry(node).or_insert_with(Peer::default);
|
|
||||||
|
|
||||||
// Don't allow messages from too far in the future.
|
|
||||||
if message.timestamp.saturating_sub(now.as_secs()) > MAX_TIME_DELTA.as_secs() {
|
|
||||||
return Err(SessionError::InvalidTimestamp(message.timestamp));
|
|
||||||
}
|
|
||||||
// Discard inventory messages we've already seen, otherwise update
|
|
||||||
// out last seen time.
|
|
||||||
if message.timestamp > peer.last_message {
|
|
||||||
peer.last_message = 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.
|
|
||||||
(
|
|
||||||
SessionState::Negotiated { git, .. },
|
|
||||||
Message::RefsAnnouncement {
|
|
||||||
node,
|
|
||||||
message,
|
|
||||||
signature,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
// FIXME: Check message timestamp.
|
|
||||||
|
|
||||||
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(SessionError::Misbehavior);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
SessionState::Negotiated { .. },
|
|
||||||
Message::NodeAnnouncement {
|
|
||||||
node,
|
|
||||||
message,
|
|
||||||
signature,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
// FIXME: Check message timestamp.
|
|
||||||
|
|
||||||
if !message.verify(&node, &signature) {
|
|
||||||
return Err(SessionError::Misbehavior);
|
|
||||||
}
|
|
||||||
log::warn!("Node announcement handling is not implemented");
|
|
||||||
}
|
|
||||||
(SessionState::Negotiated { .. }, Message::Subscribe(subscribe)) => {
|
|
||||||
self.subscribe = Some(subscribe);
|
|
||||||
}
|
|
||||||
(SessionState::Negotiated { .. }, Message::Initialize { .. }) => {
|
|
||||||
debug!(
|
|
||||||
"Disconnecting peer {} for sending us a redundant handshake message",
|
|
||||||
self.ip()
|
|
||||||
);
|
|
||||||
return Err(SessionError::Misbehavior);
|
|
||||||
}
|
|
||||||
(SessionState::Disconnected { .. }, msg) => {
|
|
||||||
debug!("Ignoring {:?} from disconnected peer {}", msg, self.ip());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::net;
|
||||||
|
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::service::peer::Session;
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface to the network reactor.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Reactor {
|
||||||
|
/// The network we're on.
|
||||||
|
network: Network,
|
||||||
|
/// Outgoing I/O queue.
|
||||||
|
io: VecDeque<Io>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reactor {
|
||||||
|
pub fn new(network: Network) -> Self {
|
||||||
|
Self {
|
||||||
|
network,
|
||||||
|
io: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit an event.
|
||||||
|
pub fn event(&mut self, event: Event) {
|
||||||
|
self.io.push_back(Io::Event(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to a peer.
|
||||||
|
pub 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnect a peer.
|
||||||
|
pub fn disconnect(&mut self, addr: net::SocketAddr, reason: DisconnectReason) {
|
||||||
|
self.io.push_back(Io::Disconnect(addr, reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, remote: net::SocketAddr, msg: Message) {
|
||||||
|
debug!("Write {:?} to {}", &msg, remote.ip());
|
||||||
|
|
||||||
|
let envelope = self.network.envelope(msg);
|
||||||
|
self.io.push_back(Io::Write(remote, vec![envelope]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) {
|
||||||
|
let envelopes = msgs
|
||||||
|
.into_iter()
|
||||||
|
.map(|msg| self.network.envelope(msg))
|
||||||
|
.collect();
|
||||||
|
self.io.push_back(Io::Write(remote, envelopes));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wakeup(&mut self, after: LocalDuration) {
|
||||||
|
self.io.push_back(Io::Wakeup(after));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Broadcast a message to a list of peers.
|
||||||
|
pub fn broadcast<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Session>) {
|
||||||
|
for peer in peers {
|
||||||
|
self.write(peer.addr, msg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relay a message to interested peers.
|
||||||
|
pub fn relay<'a>(&mut self, msg: Message, peers: impl IntoIterator<Item = &'a Session>) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn outbox(&mut self) -> &mut VecDeque<Io> {
|
||||||
|
&mut self.io
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Reactor {
|
||||||
|
type Item = Io;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.io.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,14 +4,12 @@ use bloomy::BloomFilter;
|
||||||
use quickcheck::Arbitrary;
|
use quickcheck::Arbitrary;
|
||||||
|
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use crate::identity::Id;
|
use crate::prelude::{Id, NodeId, Refs, Timestamp};
|
||||||
use crate::service::filter::{Filter, FILTER_SIZE};
|
use crate::service::filter::{Filter, FILTER_SIZE};
|
||||||
use crate::service::message::{
|
use crate::service::message::{
|
||||||
Address, Envelope, InventoryAnnouncement, Message, NodeAnnouncement, RefsAnnouncement,
|
Address, Envelope, InventoryAnnouncement, Message, NodeAnnouncement, RefsAnnouncement,
|
||||||
Subscribe,
|
Subscribe,
|
||||||
};
|
};
|
||||||
use crate::service::{NodeId, Timestamp};
|
|
||||||
use crate::storage::refs::Refs;
|
|
||||||
use crate::wire::message::MessageType;
|
use crate::wire::message::MessageType;
|
||||||
|
|
||||||
pub use radicle::test::arbitrary::*;
|
pub use radicle::test::arbitrary::*;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
|
use std::iter;
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::address_book::{KnownAddress, Source};
|
use crate::address_book::{KnownAddress, Source};
|
||||||
use crate::clock::RefClock;
|
use crate::clock::{RefClock, Timestamp};
|
||||||
use crate::collections::HashMap;
|
use crate::collections::HashMap;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::git::Url;
|
use crate::git::Url;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::config::*;
|
use crate::service::config::*;
|
||||||
use crate::service::message::*;
|
use crate::service::message::*;
|
||||||
|
use crate::service::reactor::Io;
|
||||||
use crate::service::*;
|
use crate::service::*;
|
||||||
use crate::storage::WriteStorage;
|
use crate::storage::WriteStorage;
|
||||||
use crate::test::signer::MockSigner;
|
use crate::test::signer::MockSigner;
|
||||||
|
|
@ -32,9 +34,9 @@ pub struct Peer<S> {
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, S> simulator::Peer<S> for Peer<S>
|
impl<S> simulator::Peer<S> for Peer<S>
|
||||||
where
|
where
|
||||||
S: WriteStorage<'r> + 'static,
|
S: WriteStorage + 'static,
|
||||||
{
|
{
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
@ -59,9 +61,9 @@ impl<S> DerefMut for Peer<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, S> Peer<S>
|
impl<S> Peer<S>
|
||||||
where
|
where
|
||||||
S: WriteStorage<'r> + 'static,
|
S: WriteStorage + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(name: &'static str, ip: impl Into<net::IpAddr>, storage: S) -> Self {
|
pub fn new(name: &'static str, ip: impl Into<net::IpAddr>, storage: S) -> Self {
|
||||||
let git_url = Url {
|
let git_url = Url {
|
||||||
|
|
@ -124,7 +126,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timestamp(&self) -> Timestamp {
|
pub fn timestamp(&self) -> Timestamp {
|
||||||
self.service.timestamp()
|
self.service.clock().timestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn git_url(&self) -> Url {
|
pub fn git_url(&self) -> Url {
|
||||||
|
|
@ -185,8 +187,8 @@ where
|
||||||
pub fn messages(&mut self, remote: &net::SocketAddr) -> impl Iterator<Item = Message> {
|
pub fn messages(&mut self, remote: &net::SocketAddr) -> impl Iterator<Item = Message> {
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
|
|
||||||
self.service.outbox().retain(|o| match o {
|
self.service.reactor().outbox().retain(|o| match o {
|
||||||
service::Io::Write(a, envelopes) if a == remote => {
|
Io::Write(a, envelopes) if a == remote => {
|
||||||
msgs.extend(envelopes.iter().map(|e| e.msg.clone()));
|
msgs.extend(envelopes.iter().map(|e| e.msg.clone()));
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +206,6 @@ where
|
||||||
|
|
||||||
/// Get a draining iterator over the peer's I/O outbox.
|
/// Get a draining iterator over the peer's I/O outbox.
|
||||||
pub fn outbox(&mut self) -> impl Iterator<Item = Io> + '_ {
|
pub fn outbox(&mut self) -> impl Iterator<Item = Io> + '_ {
|
||||||
self.service.outbox().drain(..)
|
iter::from_fn(|| self.service.reactor().next())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ use log::*;
|
||||||
use nakamoto_net as nakamoto;
|
use nakamoto_net as nakamoto;
|
||||||
use nakamoto_net::{Link, LocalDuration, LocalTime};
|
use nakamoto_net::{Link, LocalDuration, LocalTime};
|
||||||
|
|
||||||
use crate::service::{DisconnectReason, Envelope, Event, Io};
|
use crate::service::reactor::Io;
|
||||||
|
use crate::service::{DisconnectReason, Envelope, Event};
|
||||||
use crate::storage::WriteStorage;
|
use crate::storage::WriteStorage;
|
||||||
use crate::test::peer::Service;
|
use crate::test::peer::Service;
|
||||||
|
|
||||||
|
|
@ -191,7 +192,7 @@ pub struct Simulation<S> {
|
||||||
storage: PhantomData<S>,
|
storage: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, S: WriteStorage<'r>> Simulation<S> {
|
impl<S: WriteStorage + 'static> Simulation<S> {
|
||||||
/// Create a new simulation.
|
/// Create a new simulation.
|
||||||
pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self {
|
pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::collections::{HashMap, HashSet};
|
||||||
use crate::service::config::*;
|
use crate::service::config::*;
|
||||||
use crate::service::message::*;
|
use crate::service::message::*;
|
||||||
use crate::service::peer::*;
|
use crate::service::peer::*;
|
||||||
|
use crate::service::reactor::Io;
|
||||||
use crate::service::*;
|
use crate::service::*;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::ReadStorage;
|
use crate::storage::ReadStorage;
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ impl<S, T, G> Transport<S, T, G> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, S, T, G> nakamoto::Protocol for Transport<S, T, G>
|
impl<S, T, G> nakamoto::Protocol for Transport<S, T, G>
|
||||||
where
|
where
|
||||||
T: WriteStorage<'r> + 'static,
|
T: WriteStorage + 'static,
|
||||||
S: address_book::Store,
|
S: address_book::Store,
|
||||||
G: crypto::Signer,
|
G: crypto::Signer,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use crate::hash::Digest;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::filter;
|
use crate::service::filter;
|
||||||
|
use crate::service::reactor::Io;
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::Refs;
|
||||||
use crate::storage::refs::SignedRefs;
|
use crate::storage::refs::SignedRefs;
|
||||||
use crate::storage::WriteStorage;
|
use crate::storage::WriteStorage;
|
||||||
|
|
@ -450,10 +451,10 @@ impl<S, T, G> Wire<S, T, G> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, S, T, G> Wire<S, T, G>
|
impl<S, T, G> Wire<S, T, G>
|
||||||
where
|
where
|
||||||
S: address_book::Store,
|
S: address_book::Store,
|
||||||
T: WriteStorage<'r> + 'static,
|
T: WriteStorage + 'static,
|
||||||
G: Signer,
|
G: Signer,
|
||||||
{
|
{
|
||||||
pub fn connected(
|
pub fn connected(
|
||||||
|
|
@ -505,7 +506,7 @@ impl<S, T, G> Iterator for Wire<S, T, G> {
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self.inner.next() {
|
match self.inner.next() {
|
||||||
Some(service::Io::Write(addr, msgs)) => {
|
Some(Io::Write(addr, msgs)) => {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
for msg in msgs {
|
for msg in msgs {
|
||||||
log::debug!("Write {:?} to {}", &msg, addr.ip());
|
log::debug!("Write {:?} to {}", &msg, addr.ip());
|
||||||
|
|
@ -515,10 +516,10 @@ impl<S, T, G> Iterator for Wire<S, T, G> {
|
||||||
}
|
}
|
||||||
Some(nakamoto::Io::Write(addr, buf))
|
Some(nakamoto::Io::Write(addr, buf))
|
||||||
}
|
}
|
||||||
Some(service::Io::Event(e)) => Some(nakamoto::Io::Event(e)),
|
Some(Io::Event(e)) => Some(nakamoto::Io::Event(e)),
|
||||||
Some(service::Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
|
Some(Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
|
||||||
Some(service::Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
|
Some(Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
|
||||||
Some(service::Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
|
Some(Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
|
||||||
|
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,11 +125,11 @@ impl Doc<Verified> {
|
||||||
Ok((oid, sig))
|
Ok((oid, sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create<'r, S: WriteStorage<'r>>(
|
pub fn create<S: WriteStorage>(
|
||||||
&self,
|
&self,
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
storage: &'r S,
|
storage: &S,
|
||||||
) -> Result<(Id, git::Oid, S::Repository), Error> {
|
) -> Result<(Id, git::Oid, S::Repository), Error> {
|
||||||
// You can checkout this branch in your working copy with:
|
// You can checkout this branch in your working copy with:
|
||||||
//
|
//
|
||||||
|
|
@ -147,7 +147,7 @@ impl Doc<Verified> {
|
||||||
Ok((id, oid, repo))
|
Ok((id, oid, repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update<'r, R: WriteRepository<'r>>(
|
pub fn update<R: WriteRepository>(
|
||||||
&self,
|
&self,
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
|
|
@ -320,7 +320,7 @@ impl Doc<Unverified> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blob_at<'r, R: ReadRepository<'r>>(
|
pub fn blob_at<R: ReadRepository>(
|
||||||
commit: Oid,
|
commit: Oid,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Option<git2::Blob>, git::Error> {
|
) -> Result<Option<git2::Blob>, git::Error> {
|
||||||
|
|
@ -331,7 +331,7 @@ impl Doc<Unverified> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_at<'r, R: ReadRepository<'r>>(
|
pub fn load_at<R: ReadRepository>(
|
||||||
commit: Oid,
|
commit: Oid,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Option<(Self, Oid)>, git::Error> {
|
) -> Result<Option<(Self, Oid)>, git::Error> {
|
||||||
|
|
@ -342,7 +342,7 @@ impl Doc<Unverified> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load<'r, R: ReadRepository<'r>>(
|
pub fn load<R: ReadRepository>(
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Option<(Self, Oid)>, git::Error> {
|
) -> Result<Option<(Self, Oid)>, git::Error> {
|
||||||
|
|
@ -355,10 +355,7 @@ impl Doc<Unverified> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> Doc<V> {
|
impl<V> Doc<V> {
|
||||||
pub fn head<'r, R: ReadRepository<'r>>(
|
pub fn head<R: ReadRepository>(remote: &RemoteId, repo: &R) -> Result<Option<Oid>, git::Error> {
|
||||||
remote: &RemoteId,
|
|
||||||
repo: &R,
|
|
||||||
) -> Result<Option<Oid>, git::Error> {
|
|
||||||
let head = &git::refname!("heads").join(&*git::refs::IDENTITY_BRANCH);
|
let head = &git::refname!("heads").join(&*git::refs::IDENTITY_BRANCH);
|
||||||
if let Some(oid) = repo.reference_oid(remote, head)? {
|
if let Some(oid) = repo.reference_oid(remote, head)? {
|
||||||
Ok(Some(oid))
|
Ok(Some(oid))
|
||||||
|
|
@ -421,7 +418,7 @@ impl Identity<Oid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Identity<Untrusted> {
|
impl Identity<Untrusted> {
|
||||||
pub fn load<'r, R: ReadRepository<'r>>(
|
pub fn load<R: ReadRepository>(
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
repo: &R,
|
repo: &R,
|
||||||
) -> Result<Option<Identity<Oid>>, IdentityError> {
|
) -> Result<Option<Identity<Oid>>, IdentityError> {
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@ pub enum InitError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new radicle project from a git repository.
|
/// Initialize a new radicle project from a git repository.
|
||||||
pub fn init<'r, G: Signer, S: storage::WriteStorage<'r>>(
|
pub fn init<G: Signer, S: storage::WriteStorage>(
|
||||||
repo: &git2::Repository,
|
repo: &git2::Repository,
|
||||||
name: &str,
|
name: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
default_branch: BranchName,
|
default_branch: BranchName,
|
||||||
signer: G,
|
signer: G,
|
||||||
storage: &'r S,
|
storage: &S,
|
||||||
) -> Result<(Id, SignedRefs<Verified>), InitError> {
|
) -> Result<(Id, SignedRefs<Verified>), InitError> {
|
||||||
let pk = signer.public_key();
|
let pk = signer.public_key();
|
||||||
let delegate = identity::Delegate {
|
let delegate = identity::Delegate {
|
||||||
|
|
@ -98,7 +98,7 @@ pub enum ForkError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a local tree for an existing project, from an existing remote.
|
/// Create a local tree for an existing project, from an existing remote.
|
||||||
pub fn fork_remote<'r, G: Signer, S: storage::WriteStorage<'r>>(
|
pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
|
||||||
proj: &Id,
|
proj: &Id,
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
signer: G,
|
signer: G,
|
||||||
|
|
@ -147,7 +147,7 @@ pub fn fork_remote<'r, G: Signer, S: storage::WriteStorage<'r>>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fork<'r, G: Signer, S: storage::WriteStorage<'r>>(
|
pub fn fork<G: Signer, S: storage::WriteStorage>(
|
||||||
proj: &Id,
|
proj: &Id,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
storage: &S,
|
storage: &S,
|
||||||
|
|
@ -206,7 +206,7 @@ pub enum CloneError {
|
||||||
Checkout(#[from] CheckoutError),
|
Checkout(#[from] CheckoutError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone<'r, P: AsRef<Path>, G: Signer, S: storage::WriteStorage<'r>, H: node::Handle>(
|
pub fn clone<P: AsRef<Path>, G: Signer, S: storage::WriteStorage, H: node::Handle>(
|
||||||
proj: &Id,
|
proj: &Id,
|
||||||
path: P,
|
path: P,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
|
|
@ -232,7 +232,7 @@ pub enum CloneUrlError {
|
||||||
Checkout(#[from] CheckoutError),
|
Checkout(#[from] CheckoutError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_url<'r, P: AsRef<Path>, G: Signer, S: storage::WriteStorage<'r>>(
|
pub fn clone_url<P: AsRef<Path>, G: Signer, S: storage::WriteStorage>(
|
||||||
proj: &Id,
|
proj: &Id,
|
||||||
url: &git::Url,
|
url: &git::Url,
|
||||||
path: P,
|
path: P,
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,8 @@ pub trait ReadStorage {
|
||||||
fn inventory(&self) -> Result<Inventory, Error>;
|
fn inventory(&self) -> Result<Inventory, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WriteStorage<'r>: ReadStorage {
|
pub trait WriteStorage: ReadStorage {
|
||||||
type Repository: WriteRepository<'r>;
|
type Repository: WriteRepository;
|
||||||
|
|
||||||
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error>;
|
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error>;
|
||||||
fn sign_refs<G: Signer>(
|
fn sign_refs<G: Signer>(
|
||||||
|
|
@ -231,11 +231,10 @@ pub trait WriteStorage<'r>: ReadStorage {
|
||||||
repository: &Self::Repository,
|
repository: &Self::Repository,
|
||||||
signer: G,
|
signer: G,
|
||||||
) -> Result<SignedRefs<Verified>, Error>;
|
) -> Result<SignedRefs<Verified>, Error>;
|
||||||
|
fn fetch(&self, proj_id: &Id, remote: &Url) -> Result<Vec<RefUpdate>, FetchError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ReadRepository<'r> {
|
pub trait ReadRepository {
|
||||||
type Remotes: Iterator<Item = Result<(RemoteId, Remote<Verified>), refs::Error>> + 'r;
|
|
||||||
|
|
||||||
fn is_empty(&self) -> Result<bool, git2::Error>;
|
fn is_empty(&self) -> Result<bool, git2::Error>;
|
||||||
fn path(&self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result<git2::Blob<'a>, git_ext::Error>;
|
fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result<git2::Blob<'a>, git_ext::Error>;
|
||||||
|
|
@ -253,13 +252,13 @@ pub trait ReadRepository<'r> {
|
||||||
) -> Result<Option<Oid>, git2::Error>;
|
) -> Result<Option<Oid>, git2::Error>;
|
||||||
fn references(&self, remote: &RemoteId) -> Result<Refs, Error>;
|
fn references(&self, remote: &RemoteId) -> Result<Refs, Error>;
|
||||||
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error>;
|
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error>;
|
||||||
fn remotes(&'r self) -> Result<Self::Remotes, git2::Error>;
|
fn remotes(&self) -> Result<Remotes<Verified>, refs::Error>;
|
||||||
/// Return the project associated with this repository.
|
/// Return the project associated with this repository.
|
||||||
fn project(&self) -> Result<identity::Doc<Verified>, Error>;
|
fn project(&self) -> Result<identity::Doc<Verified>, Error>;
|
||||||
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError>;
|
fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WriteRepository<'r>: ReadRepository<'r> {
|
pub trait WriteRepository: ReadRepository {
|
||||||
fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, FetchError>;
|
fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, FetchError>;
|
||||||
fn raw(&self) -> &git2::Repository;
|
fn raw(&self) -> &git2::Repository;
|
||||||
}
|
}
|
||||||
|
|
@ -286,10 +285,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, T, S> WriteStorage<'r> for T
|
impl<T, S> WriteStorage for T
|
||||||
where
|
where
|
||||||
T: Deref<Target = S>,
|
T: Deref<Target = S>,
|
||||||
S: WriteStorage<'r> + 'static,
|
S: WriteStorage + 'static,
|
||||||
{
|
{
|
||||||
type Repository = S::Repository;
|
type Repository = S::Repository;
|
||||||
|
|
||||||
|
|
@ -304,6 +303,10 @@ where
|
||||||
) -> Result<SignedRefs<Verified>, Error> {
|
) -> Result<SignedRefs<Verified>, Error> {
|
||||||
self.deref().sign_refs(repository, signer)
|
self.deref().sign_refs(repository, signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch(&self, proj_id: &Id, remote: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
|
self.deref().fetch(proj_id, remote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::identity::{Doc, Id};
|
||||||
use crate::storage::refs;
|
use crate::storage::refs;
|
||||||
use crate::storage::refs::{Refs, SignedRefs};
|
use crate::storage::refs::{Refs, SignedRefs};
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
Error, FetchError, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository,
|
Error, FetchError, Inventory, ReadRepository, ReadStorage, Remote, Remotes, WriteRepository,
|
||||||
WriteStorage,
|
WriteStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl ReadStorage for Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> WriteStorage<'r> for Storage {
|
impl WriteStorage for Storage {
|
||||||
type Repository = Repository;
|
type Repository = Repository;
|
||||||
|
|
||||||
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error> {
|
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error> {
|
||||||
|
|
@ -99,6 +99,19 @@ impl<'r> WriteStorage<'r> for Storage {
|
||||||
|
|
||||||
Ok(signed)
|
Ok(signed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch(&self, proj_id: &Id, remote: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
|
let mut repo = self.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()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage {
|
impl Storage {
|
||||||
|
|
@ -338,11 +351,28 @@ impl Repository {
|
||||||
);
|
);
|
||||||
Ok(iter)
|
Ok(iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remotes(
|
||||||
|
&self,
|
||||||
|
) -> Result<
|
||||||
|
impl Iterator<Item = Result<(RemoteId, Remote<Verified>), refs::Error>> + '_,
|
||||||
|
git2::Error,
|
||||||
|
> {
|
||||||
|
let remotes = self.backend.references_glob(SIGNATURES_GLOB.as_str())?.map(
|
||||||
|
|reference| -> Result<_, _> {
|
||||||
|
let r = reference?;
|
||||||
|
let name = r.name().ok_or(refs::Error::InvalidRef)?;
|
||||||
|
let (id, _) = git::parse_ref::<RemoteId>(name)?;
|
||||||
|
let remote = self.remote(&id)?;
|
||||||
|
|
||||||
|
Ok((id, remote))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(remotes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> ReadRepository<'r> for Repository {
|
impl ReadRepository for Repository {
|
||||||
type Remotes = Box<dyn Iterator<Item = Result<(RemoteId, Remote<Verified>), refs::Error>> + 'r>;
|
|
||||||
|
|
||||||
fn is_empty(&self) -> Result<bool, git2::Error> {
|
fn is_empty(&self) -> Result<bool, git2::Error> {
|
||||||
let some = self.remotes()?.next().is_some();
|
let some = self.remotes()?.next().is_some();
|
||||||
Ok(!some)
|
Ok(!some)
|
||||||
|
|
@ -425,19 +455,12 @@ impl<'r> ReadRepository<'r> for Repository {
|
||||||
Ok(refs.into())
|
Ok(refs.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remotes(&'r self) -> Result<Self::Remotes, git2::Error> {
|
fn remotes(&self) -> Result<Remotes<Verified>, refs::Error> {
|
||||||
let iter = self.backend.references_glob(SIGNATURES_GLOB.as_str())?.map(
|
let mut remotes = Vec::new();
|
||||||
|reference| -> Result<(RemoteId, Remote<Verified>), refs::Error> {
|
for remote in Repository::remotes(self)? {
|
||||||
let r = reference?;
|
remotes.push(remote?);
|
||||||
let name = r.name().ok_or(refs::Error::InvalidRef)?;
|
}
|
||||||
let (id, _) = git::parse_ref::<RemoteId>(name)?;
|
Ok(Remotes::from_iter(remotes))
|
||||||
let remote = self.remote(&id)?;
|
|
||||||
|
|
||||||
Ok((id, remote))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Box::new(iter))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project(&self) -> Result<Doc<Verified>, Error> {
|
fn project(&self) -> Result<Doc<Verified>, Error> {
|
||||||
|
|
@ -449,7 +472,7 @@ impl<'r> ReadRepository<'r> for Repository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> WriteRepository<'r> for Repository {
|
impl WriteRepository for Repository {
|
||||||
/// Fetch all remotes of a project from the given URL.
|
/// Fetch all remotes of a project from the given URL.
|
||||||
fn fetch(&mut self, url: &git::Url) -> Result<Vec<RefUpdate>, FetchError> {
|
fn fetch(&mut self, url: &git::Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
// TODO: Have function to fetch specific remotes.
|
// TODO: Have function to fetch specific remotes.
|
||||||
|
|
|
||||||
|
|
@ -213,9 +213,9 @@ impl SignedRefs<Unverified> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignedRefs<Verified> {
|
impl SignedRefs<Verified> {
|
||||||
pub fn load<'r, S>(remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
pub fn load<S>(remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
S: ReadRepository<'r>,
|
S: ReadRepository,
|
||||||
{
|
{
|
||||||
if let Some(oid) = repo.reference_oid(remote, &SIGNATURE_REF)? {
|
if let Some(oid) = repo.reference_oid(remote, &SIGNATURE_REF)? {
|
||||||
Self::load_at(oid, remote, repo)
|
Self::load_at(oid, remote, repo)
|
||||||
|
|
@ -224,9 +224,9 @@ impl SignedRefs<Verified> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_at<'r, S>(oid: Oid, remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
pub fn load_at<S>(oid: Oid, remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
S: storage::ReadRepository<'r>,
|
S: storage::ReadRepository,
|
||||||
{
|
{
|
||||||
let refs = repo.blob_at(oid, Path::new(REFS_BLOB_PATH))?;
|
let refs = repo.blob_at(oid, Path::new(REFS_BLOB_PATH))?;
|
||||||
let signature = repo.blob_at(oid, Path::new(SIGNATURE_BLOB_PATH))?;
|
let signature = repo.blob_at(oid, Path::new(SIGNATURE_BLOB_PATH))?;
|
||||||
|
|
@ -248,7 +248,7 @@ impl SignedRefs<Verified> {
|
||||||
|
|
||||||
/// Save the signed refs to disk.
|
/// Save the signed refs to disk.
|
||||||
/// This creates a new commit on the signed refs branch, and updates the branch pointer.
|
/// This creates a new commit on the signed refs branch, and updates the branch pointer.
|
||||||
pub fn save<'r, S: WriteRepository<'r>>(
|
pub fn save<S: WriteRepository>(
|
||||||
&self,
|
&self,
|
||||||
// TODO: This should be part of the signed refs.
|
// TODO: This should be part of the signed refs.
|
||||||
remote: &RemoteId,
|
remote: &RemoteId,
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new repository at the given path, and initialize it into a project.
|
/// Create a new repository at the given path, and initialize it into a project.
|
||||||
pub fn project<'r, P: AsRef<Path>, S: WriteStorage<'r>, G: Signer>(
|
pub fn project<P: AsRef<Path>, S: WriteStorage, G: Signer>(
|
||||||
path: P,
|
path: P,
|
||||||
storage: &'r S,
|
storage: &S,
|
||||||
signer: G,
|
signer: G,
|
||||||
) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> {
|
) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> {
|
||||||
let (repo, head) = repository(path);
|
let (repo, head) = repository(path);
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl ReadStorage for MockStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WriteStorage<'_> for MockStorage {
|
impl WriteStorage for MockStorage {
|
||||||
type Repository = MockRepository;
|
type Repository = MockRepository;
|
||||||
|
|
||||||
fn repository(&self, _proj: &Id) -> Result<Self::Repository, Error> {
|
fn repository(&self, _proj: &Id) -> Result<Self::Repository, Error> {
|
||||||
|
|
@ -67,13 +67,15 @@ impl WriteStorage<'_> for MockStorage {
|
||||||
) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
|
) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch(&self, _proj_id: &Id, _remote: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MockRepository {}
|
pub struct MockRepository {}
|
||||||
|
|
||||||
impl ReadRepository<'_> for MockRepository {
|
impl ReadRepository for MockRepository {
|
||||||
type Remotes = std::iter::Empty<Result<(RemoteId, Remote<Verified>), refs::Error>>;
|
|
||||||
|
|
||||||
fn is_empty(&self) -> Result<bool, git2::Error> {
|
fn is_empty(&self) -> Result<bool, git2::Error> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +88,7 @@ impl ReadRepository<'_> for MockRepository {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remotes(&self) -> Result<Self::Remotes, git2::Error> {
|
fn remotes(&self) -> Result<Remotes<Verified>, refs::Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +139,7 @@ impl ReadRepository<'_> for MockRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WriteRepository<'_> for MockRepository {
|
impl WriteRepository for MockRepository {
|
||||||
fn fetch(&mut self, _url: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
fn fetch(&mut self, _url: &Url) -> Result<Vec<RefUpdate>, FetchError> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue