node: Implement worker thread handover

Also implements listener behavior.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Dr. Maxim Orlovsky 2022-12-22 21:36:20 +01:00 committed by Alexis Sellier
parent e49226c500
commit a7b1c19746
No known key found for this signature in database
18 changed files with 453 additions and 203 deletions

30
Cargo.lock generated
View File

@ -1367,6 +1367,18 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "io-reactor"
version = "0.1.0"
source = "git+https://github.com/cyphernet-wg/rust-netservices#2a09c1714bea9baf8a96b9bd09d2dac4181a5019"
dependencies = [
"amplify",
"crossbeam-channel",
"libc",
"popol",
"socket2",
]
[[package]] [[package]]
name = "iri-string" name = "iri-string"
version = "0.6.0" version = "0.6.0"
@ -1606,12 +1618,12 @@ dependencies = [
[[package]] [[package]]
name = "netservices" name = "netservices"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/cyphernet-wg/rust-netservices#045ab140fd33ab3008a0ce8c86d8b9d01b08662c" source = "git+https://github.com/cyphernet-wg/rust-netservices#2a09c1714bea9baf8a96b9bd09d2dac4181a5019"
dependencies = [ dependencies = [
"amplify", "amplify",
"cyphernet", "cyphernet",
"io-reactor",
"libc", "libc",
"poll-reactor",
"socket2", "socket2",
] ]
@ -1915,18 +1927,6 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "poll-reactor"
version = "0.1.0"
source = "git+https://github.com/cyphernet-wg/rust-netservices#045ab140fd33ab3008a0ce8c86d8b9d01b08662c"
dependencies = [
"amplify",
"crossbeam-channel",
"libc",
"popol",
"socket2",
]
[[package]] [[package]]
name = "popol" name = "popol"
version = "1.0.0" version = "1.0.0"
@ -2222,12 +2222,12 @@ dependencies = [
"cyphernet", "cyphernet",
"fastrand", "fastrand",
"git-ref-format", "git-ref-format",
"io-reactor",
"lexopt", "lexopt",
"log", "log",
"nakamoto-net", "nakamoto-net",
"netservices", "netservices",
"nonempty 0.8.1", "nonempty 0.8.1",
"poll-reactor",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle", "radicle",

View File

@ -37,7 +37,7 @@ version = "0.3.0"
git = "https://github.com/cyphernet-wg/rust-cyphernet" git = "https://github.com/cyphernet-wg/rust-cyphernet"
version = "0.1.0" version = "0.1.0"
[patch.crates-io.poll-reactor] [patch.crates-io.io-reactor]
git = "https://github.com/cyphernet-wg/rust-netservices" git = "https://github.com/cyphernet-wg/rust-netservices"
version = "0.1.0" version = "0.1.0"

View File

@ -1,4 +1,5 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::sync::Arc;
use std::{fmt, ops::Deref, str::FromStr}; use std::{fmt, ops::Deref, str::FromStr};
use ed25519_compact as ed25519; use ed25519_compact as ed25519;
@ -26,28 +27,28 @@ pub type SharedSecret = [u8; 32];
/// Trait alias used for DiffieHellman key exchange. /// Trait alias used for DiffieHellman key exchange.
#[cfg(feature = "cyphernet")] #[cfg(feature = "cyphernet")]
pub trait Negotiator: pub trait Negotiator:
cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone + Send
{ {
} }
#[cfg(feature = "cyphernet")] #[cfg(feature = "cyphernet")]
impl<T> Negotiator for T where impl<T> Negotiator for T where
T: cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone T: cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone + Send
{ {
} }
/// Error returned if signing fails, eg. due to an HSM or KMS. /// Error returned if signing fails, eg. due to an HSM or KMS.
#[derive(Debug, Error)] #[derive(Debug, Clone, Error)]
#[error(transparent)] #[error(transparent)]
pub struct SignerError { pub struct SignerError {
#[from] #[from]
source: Box<dyn std::error::Error + Send + Sync>, source: Arc<dyn std::error::Error + Send + Sync>,
} }
impl SignerError { impl SignerError {
pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self { pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self {
Self { Self {
source: Box::new(source), source: Arc::new(source),
} }
} }
} }

View File

@ -71,3 +71,14 @@ impl Signer for MockSigner {
Ok(self.sign(msg)) Ok(self.sign(msg))
} }
} }
#[cfg(feature = "cyphernet")]
impl cyphernet::crypto::Ecdh for MockSigner {
type Pk = PublicKey;
type Secret = crate::SharedSecret;
type Err = crate::Error;
fn ecdh(&self, _pk: &Self::Pk) -> Result<Self::Secret, Self::Err> {
Ok([0; 32])
}
}

View File

@ -6,7 +6,7 @@ authors = ["Alexis Sellier <alexis@radicle.xyz>"]
edition = "2021" edition = "2021"
[features] [features]
test = ["radicle/test", "radicle-crypto/test", "qcheck"] test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "qcheck"]
[dependencies] [dependencies]
amplify = { version = "4.0.0-beta.1", default-features = false, features = ["std"] } amplify = { version = "4.0.0-beta.1", default-features = false, features = ["std"] }
@ -22,9 +22,9 @@ git-ref-format = { version = "0", features = ["serde", "macro"] }
lexopt = { version = "0.2.1" } lexopt = { version = "0.2.1" }
log = { version = "0.4.17", features = ["std"] } log = { version = "0.4.17", features = ["std"] }
nakamoto-net = { version = "0.3.0" } nakamoto-net = { version = "0.3.0" }
netservices = { version = "0", features = ["poll-reactor", "socket2"] } netservices = { version = "0", features = ["io-reactor", "socket2"] }
nonempty = { version = "0.8.1", features = ["serialize"] } nonempty = { version = "0.8.1", features = ["serialize"] }
poll-reactor = { version = "0", features = ["popol", "socket2"] } io-reactor = { version = "0", features = ["popol", "socket2"] }
qcheck = { version = "1", default-features = false, optional = true } qcheck = { version = "1", default-features = false, optional = true }
sqlite = { version = "0.30.3" } sqlite = { version = "0.30.3" }
sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking sqlite3-src = { version = "0.4.0", features = ["bundled"] } # Ensures static linking
@ -41,6 +41,6 @@ features = ["sql"]
[dev-dependencies] [dev-dependencies]
radicle = { path = "../radicle", version = "*", features = ["test"] } radicle = { path = "../radicle", version = "*", features = ["test"] }
radicle-crypto = { path = "../radicle-crypto", version = "*", features = ["test"] } radicle-crypto = { path = "../radicle-crypto", version = "*", features = ["test", "cyphernet"] }
qcheck = { version = "1", default-features = false } qcheck = { version = "1", default-features = false }
qcheck-macros = { version = "1", default-features = false } qcheck-macros = { version = "1", default-features = false }

View File

@ -46,25 +46,24 @@ impl<T> From<chan::SendError<T>> for Error {
} }
} }
pub struct Handle { pub struct Handle<T: reactor::Handler> {
pub(crate) controller: reactor::Controller<service::Command>, pub(crate) controller: reactor::Controller<T>,
} }
impl From<reactor::Controller<service::Command>> for Handle { impl<T: reactor::Handler> From<reactor::Controller<T>> for Handle<T> {
fn from(controller: reactor::Controller<service::Command>) -> Handle { fn from(controller: reactor::Controller<T>) -> Handle<T> {
Handle { controller } Handle { controller }
} }
} }
impl Handle { impl<T: reactor::Handler<Command = service::Command>> Handle<T> {
fn command(&self, cmd: service::Command) -> Result<(), Error> { fn command(&self, cmd: service::Command) -> Result<(), Error> {
self.controller.send(cmd)?; self.controller.send(cmd)?;
Ok(()) Ok(())
} }
} }
impl radicle::node::Handle for Handle { impl<T: reactor::Handler<Command = service::Command>> radicle::node::Handle for Handle<T> {
type Session = Session; type Session = Session;
type FetchLookup = FetchLookup; type FetchLookup = FetchLookup;
type Error = Error; type Error = Error;

View File

@ -15,6 +15,7 @@ pub mod test;
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;
pub mod wire; pub mod wire;
pub mod worker;
pub use nakamoto_net::{Io, Link, LocalDuration, LocalTime}; pub use nakamoto_net::{Io, Link, LocalDuration, LocalTime};
pub use radicle::{collections, crypto, git, identity, node, profile, rad, storage}; pub use radicle::{collections, crypto, git, identity, node, profile, rad, storage};

View File

@ -3,16 +3,19 @@ use std::{env, net, process, thread};
use anyhow::Context as _; use anyhow::Context as _;
use cyphernet::addr::PeerAddr; use cyphernet::addr::PeerAddr;
use nakamoto_net::{LocalDuration, LocalTime}; use nakamoto_net::{LocalDuration, LocalTime};
use netservices::wire::NetAccept;
use reactor::poller::popol; use reactor::poller::popol;
use reactor::Reactor; use reactor::Reactor;
use radicle::profile; use radicle::profile;
use radicle::storage::WriteStorage;
use radicle_node::client::handle::Handle; use radicle_node::client::handle::Handle;
use radicle_node::client::{ADDRESS_DB_FILE, NODE_DIR, ROUTING_DB_FILE, TRACKING_DB_FILE}; use radicle_node::client::{ADDRESS_DB_FILE, NODE_DIR, ROUTING_DB_FILE, TRACKING_DB_FILE};
use radicle_node::crypto::ssh::keystore::MemorySigner; use radicle_node::crypto::ssh::keystore::MemorySigner;
use radicle_node::prelude::{Address, NodeId}; use radicle_node::prelude::{Address, NodeId};
use radicle_node::service::{routing, tracking}; use radicle_node::service::{routing, tracking, FetchResult};
use radicle_node::wire::Transport; use radicle_node::wire::Transport;
use radicle_node::worker::{WorkerReq, WorkerResp};
use radicle_node::{address, control, logger, service}; use radicle_node::{address, control, logger, service};
#[derive(Debug)] #[derive(Debug)]
@ -117,17 +120,51 @@ fn main() -> anyhow::Result<()> {
let tracking = tracking::Config::open(tracking_db)?; let tracking = tracking::Config::open(tracking_db)?;
log::info!("Initializing service ({:?})..", network); log::info!("Initializing service ({:?})..", network);
let worker_storage = storage.clone();
let service = service::Service::new( let service = service::Service::new(
config, clock, routing, storage, addresses, tracking, signer, rng, config, clock, routing, storage, addresses, tracking, signer, rng,
); );
let wire = Transport::new(service, negotiator, proxy_addr, clock); let (worker_send, worker_recv) = crossbeam_channel::unbounded::<WorkerReq<MemorySigner>>();
let reactor = Reactor::new(wire, popol::Poller::new()); let workers = thread::spawn(move || {
let handle = Handle::from(reactor.controller()); while let Ok(WorkerReq {
fetch,
session,
channel,
..
}) = worker_recv.recv()
{
let result = match worker_storage.repository(fetch.repo) {
Ok(_) => todo!(),
Err(err) => FetchResult::Error {
from: fetch.remote,
error: err.into(),
},
};
if channel.send(WorkerResp { result, session }).is_err() {
log::error!("Unable to report fetch result: worker channel disconnected");
}
}
});
let wire = Transport::new(service, worker_send, negotiator.clone(), proxy_addr, clock);
let reactor =
Reactor::new(wire, popol::Poller::new()).expect("unable to instantiate P2P reactor");
let controller = reactor.controller();
for socket in options.listen {
let listener = NetAccept::bind(socket, negotiator.clone())?;
controller.register_listener(listener)?;
log::info!("Listening on {socket}..");
}
let handle = Handle::from(controller);
let control = thread::spawn(move || control::listen(node, handle)); let control = thread::spawn(move || control::listen(node, handle));
control.join().unwrap()?; control.join().unwrap()?;
reactor.join().unwrap(); reactor.join().unwrap();
workers.join().unwrap();
Ok(()) Ok(())
} }

View File

@ -27,7 +27,7 @@ use crate::address;
use crate::address::AddressBook; use crate::address::AddressBook;
use crate::clock::Timestamp; use crate::clock::Timestamp;
use crate::crypto; use crate::crypto;
use crate::crypto::{Signer, Verified}; use crate::crypto::{Negotiator, Signer, Verified};
use crate::git; use crate::git;
use crate::identity::{Doc, Id}; use crate::identity::{Doc, Id};
use crate::node; use crate::node;
@ -107,6 +107,7 @@ pub enum FetchError {
} }
/// Result of looking up seeds in our routing table. /// Result of looking up seeds in our routing table.
/// This object is sent back to the caller who initiated the fetch.
#[derive(Debug)] #[derive(Debug)]
pub enum FetchLookup { pub enum FetchLookup {
/// Found seeds for the given project. /// Found seeds for the given project.
@ -244,7 +245,7 @@ where
R: routing::Store, R: routing::Store,
A: address::Store, A: address::Store,
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: crypto::Signer, G: Signer + Negotiator,
{ {
pub fn new( pub fn new(
config: Config, config: Config,
@ -282,17 +283,6 @@ where
} }
} }
pub fn seeds(&self, id: &Id) -> Vec<(NodeId, &Session)> {
if let Ok(seeds) = self.routing.get(id) {
seeds
.into_iter()
.filter_map(|id| self.sessions.by_id(&id).map(|p| (id, p)))
.collect()
} else {
vec![]
}
}
/// Track a repository. /// Track a repository.
/// Returns whether or not the tracking policy was updated. /// Returns whether or not the tracking policy was updated.
pub fn track_repo(&mut self, id: &Id, scope: tracking::Scope) -> Result<bool, tracking::Error> { pub fn track_repo(&mut self, id: &Id, scope: tracking::Scope) -> Result<bool, tracking::Error> {
@ -446,51 +436,36 @@ where
return; return;
} }
let seeds = self.seeds(&id); let Ok(seeds) = self.routing.get(&id) else {
let Some(seeds) = NonEmpty::from_vec(seeds) else { todo!();
log::error!("No seeds found for {}", id); };
let Some(seeds) = NonEmpty::from_vec(seeds.into_iter().collect()) else {
log::warn!("No seeds found for {}", id);
resp.send(FetchLookup::NotFound).ok(); resp.send(FetchLookup::NotFound).ok();
return; return;
}; };
log::debug!("Found {} seeds for {}", seeds.len(), id); log::debug!("Found {} seeds for {}", seeds.len(), id);
let mut repo = match self.storage.repository(id) { // FIXME: Get results back to user.
Ok(repo) => repo, let (_, results) = chan::bounded(seeds.len());
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 { resp.send(FetchLookup::Found {
seeds: seeds.clone().map(|(_, peer)| peer.id), seeds: seeds.clone(),
results, results,
}) })
.ok(); .ok();
// TODO: Limit the number of seeds we fetch from? Randomize? // TODO: Limit the number of seeds we fetch from? Randomize?
for (peer_id, peer) in seeds { for seed in seeds {
match repo.fetch(&peer_id, Namespaces::default()) { let session = self.sessions.get_mut(&seed).unwrap();
Ok(updated) => { if let Some(upgrade) = session.upgrade(id) {
results_ self.reactor.write(session.id, upgrade);
.send(FetchResult::Fetched { self.reactor
from: peer.id, .fetch(session.id, id, Namespaces::default(), true);
updated, } else {
}) // TODO: If we can't upgrade, it's because we're already fetching from
.ok(); // this peer. So we need to queue the request, or find another peer.
} todo!();
Err(err) => {
results_
.send(FetchResult::Error {
from: peer.id,
error: err.into(),
})
.ok();
}
} }
} }
} }
@ -531,17 +506,19 @@ where
} }
} }
pub fn fetch_complete(&mut self, _result: FetchResult) {
// TODO(cloudhead): handle completed job with service business logic
}
pub fn accepted(&mut self, _addr: net::SocketAddr) { pub fn accepted(&mut self, _addr: net::SocketAddr) {
// Inbound connection attempt. // Inbound connection attempt.
} }
pub fn attempted(&mut self, id: NodeId, _addr: &Address) { pub fn attempted(&mut self, id: NodeId, _addr: &Address) {
let persistent = self.config.is_persistent(&id); let persistent = self.config.is_persistent(&id);
let peer = self let peer = self.sessions.entry(id).or_insert_with(|| {
.sessions Session::new(id, Link::Outbound, persistent, self.rng.clone(), self.clock)
.entry(id) });
.or_insert_with(|| Session::new(id, Link::Outbound, persistent, self.rng.clone()));
peer.attempted(); peer.attempted();
} }
@ -575,6 +552,7 @@ where
Link::Inbound, Link::Inbound,
self.config.is_persistent(&remote), self.config.is_persistent(&remote),
self.rng.clone(), self.rng.clone(),
self.clock,
), ),
); );
} }
@ -823,9 +801,15 @@ where
debug!("Received {:?} from {}", &message, peer.id); debug!("Received {:?} from {}", &message, peer.id);
match (&mut peer.state, message) { match (&mut peer.state, message) {
(session::State::Initial, Message::Initialize {}) => { (session::State::Connected { initialized, .. }, Message::Initialize {}) => {
// Nb. This is a very primitive handshake. Eventually we should have anyhow // Already initialized!
// extra "acknowledgment" message sent when the `Initialize` is well received. if *initialized {
debug!(
"Disconnecting peer {} for sending us a message before initializing",
peer.id
);
return Err(session::Error::Misbehavior);
}
if peer.link.is_inbound() { if peer.link.is_inbound() {
self.reactor.write_all( self.reactor.write_all(
peer.id, peer.id,
@ -838,25 +822,14 @@ where
), ),
); );
} }
*initialized = true;
// Nb. we don't set the peer timestamp here, since it is going to be // 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 // set after the first message is received only. Setting it here would
// mean that messages received right after the handshake could be ignored. // mean that messages received right after the handshake could be ignored.
peer.state = session::State::Negotiated {
id: *remote,
since: self.clock,
ping: Default::default(),
};
}
(session::State::Initial, _) => {
debug!(
"Disconnecting peer {} for sending us a message before handshake",
peer.id
);
return Err(session::Error::Misbehavior);
} }
// Process a peer announcement. // Process a peer announcement.
(session::State::Negotiated { id: relayer, .. }, Message::Announcement(ann)) => { (session::State::Connected { .. }, Message::Announcement(ann)) => {
let relayer = *relayer; let relayer = peer.id;
// Returning true here means that the message should be relayed. // Returning true here means that the message should be relayed.
if self.handle_announcement(&relayer, &ann)? { if self.handle_announcement(&relayer, &ann)? {
@ -875,7 +848,7 @@ where
return Ok(()); return Ok(());
} }
} }
(session::State::Negotiated { .. }, Message::Subscribe(subscribe)) => { (session::State::Connected { .. }, Message::Subscribe(subscribe)) => {
for msg in self for msg in self
.gossip .gossip
.filtered(&subscribe.filter, subscribe.since, subscribe.until) .filtered(&subscribe.filter, subscribe.since, subscribe.until)
@ -884,14 +857,7 @@ where
} }
peer.subscribe = Some(subscribe); peer.subscribe = Some(subscribe);
} }
(session::State::Negotiated { .. }, Message::Initialize { .. }) => { (session::State::Connected { .. }, Message::Ping(Ping { ponglen, .. })) => {
debug!(
"Disconnecting peer {} for sending us a redundant handshake message",
peer.id
);
return Err(session::Error::Misbehavior);
}
(session::State::Negotiated { .. }, Message::Ping(Ping { ponglen, .. })) => {
// Ignore pings which ask for too much data. // Ignore pings which ask for too much data.
if ponglen > Ping::MAX_PONG_ZEROES { if ponglen > Ping::MAX_PONG_ZEROES {
return Ok(()); return Ok(());
@ -903,13 +869,18 @@ where
}, },
); );
} }
(session::State::Negotiated { ping, .. }, Message::Pong { zeroes }) => { (session::State::Connected { ping, .. }, Message::Pong { zeroes }) => {
if let session::PingState::AwaitingResponse(ponglen) = *ping { if let session::PingState::AwaitingResponse(ponglen) = *ping {
if (ponglen as usize) == zeroes.len() { if (ponglen as usize) == zeroes.len() {
*ping = session::PingState::Ok; *ping = session::PingState::Ok;
} }
} }
} }
(session::State::Connected { .. }, Message::Upgrade { repo }) => {
// All we need is to instruct the transport to handover to the worker
self.reactor
.fetch(*remote, repo, Namespaces::default(), false);
}
(session::State::Disconnected { .. }, msg) => { (session::State::Disconnected { .. }, msg) => {
debug!("Ignoring {:?} from disconnected peer {}", msg, peer.id); debug!("Ignoring {:?} from disconnected peer {}", msg, peer.id);
} }
@ -1030,28 +1001,14 @@ where
} }
fn choose_addresses(&mut self) -> Vec<(NodeId, Address)> { fn choose_addresses(&mut self) -> Vec<(NodeId, Address)> {
// TODO(cloudhead): Remove once noise is implemented. let sessions = self
let mut initializing: Vec<NodeId> = Vec::new(); .sessions
let mut negotiated: HashMap<NodeId, &Session> = HashMap::new(); .values()
for s in self.sessions.values() { .filter(|s| s.is_connected() && s.link.is_outbound())
if !s.link.is_outbound() { .map(|s| (s.id, s))
continue; .collect::<HashMap<_, _>>();
}
match s.state {
// TODO(cloudhead): Remove when we have noise handshake.
session::State::Initial => {
initializing.push(s.id);
}
session::State::Negotiated { id, .. } => {
negotiated.insert(id, s);
}
session::State::Disconnected { .. } => {}
}
}
let wanted = TARGET_OUTBOUND_PEERS let wanted = TARGET_OUTBOUND_PEERS.saturating_sub(sessions.len());
.saturating_sub(initializing.len())
.saturating_sub(negotiated.len());
if wanted == 0 { if wanted == 0 {
return Vec::new(); return Vec::new();
} }
@ -1059,9 +1016,7 @@ where
self.addresses self.addresses
.entries() .entries()
.unwrap() .unwrap()
.filter(|(node_id, _)| { .filter(|(node_id, _)| !sessions.contains_key(node_id))
!initializing.contains(node_id) && !negotiated.contains_key(node_id)
})
.take(wanted) .take(wanted)
.map(|(n, s)| (n, s.addr)) .map(|(n, s)| (n, s.addr))
.collect() .collect()
@ -1253,23 +1208,19 @@ impl Sessions {
Self(AddressBook::new(rng)) Self(AddressBook::new(rng))
} }
pub fn by_id(&self, id: &NodeId) -> Option<&Session> {
self.0.values().find(|p| p.node_id() == Some(*id))
}
/// Iterator over fully negotiated peers. /// Iterator over fully negotiated peers.
pub fn negotiated(&self) -> impl Iterator<Item = (&NodeId, &Session)> + Clone { pub fn negotiated(&self) -> impl Iterator<Item = (&NodeId, &Session)> + Clone {
self.0 self.0
.iter() .iter()
.filter_map(move |(_, sess)| match &sess.state { .filter_map(move |(id, sess)| match &sess.state {
session::State::Negotiated { id, .. } => Some((id, sess)), session::State::Connected { .. } => Some((id, sess)),
_ => None, _ => None,
}) })
} }
/// Iterator over mutable fully negotiated peers. /// Iterator over mutable fully negotiated peers.
pub fn negotiated_mut(&mut self) -> impl Iterator<Item = (&NodeId, &mut Session)> { pub fn negotiated_mut(&mut self) -> impl Iterator<Item = (&NodeId, &mut Session)> {
self.0.iter_mut().filter(move |(_, p)| p.is_negotiated()) self.0.iter_mut().filter(move |(_, p)| p.is_connected())
} }
} }

View File

@ -331,6 +331,9 @@ pub enum Message {
/// The pong payload. /// The pong payload.
zeroes: ZeroBytes, zeroes: ZeroBytes,
}, },
/// Upgrade session to Git protocol for the given repository.
Upgrade { repo: Id },
} }
impl Message { impl Message {
@ -416,6 +419,7 @@ impl fmt::Debug for Message {
} }
Self::Ping(Ping { ponglen, zeroes }) => write!(f, "Ping({ponglen}, {:?})", zeroes), Self::Ping(Ping { ponglen, zeroes }) => write!(f, "Ping({ponglen}, {:?})", zeroes),
Self::Pong { zeroes } => write!(f, "Pong({:?})", zeroes), Self::Pong { zeroes } => write!(f, "Pong({:?})", zeroes),
Self::Upgrade { repo } => write!(f, "Upgrade({repo})"),
} }
} }
} }

View File

@ -4,6 +4,7 @@ use log::*;
use crate::prelude::*; use crate::prelude::*;
use crate::service::session::Session; use crate::service::session::Session;
use crate::storage::{FetchError, Namespaces, RefUpdate};
use super::message::{Announcement, AnnouncementMessage}; use super::message::{Announcement, AnnouncementMessage};
@ -16,12 +17,37 @@ pub enum Io {
Connect(NodeId, Address), Connect(NodeId, Address),
/// Disconnect from a peer. /// Disconnect from a peer.
Disconnect(NodeId, DisconnectReason), Disconnect(NodeId, DisconnectReason),
/// Fetch repository data from a peer.
Fetch(Fetch),
/// Ask for a wakeup in a specified amount of time. /// Ask for a wakeup in a specified amount of time.
Wakeup(LocalDuration), Wakeup(LocalDuration),
/// Emit an event. /// Emit an event.
Event(Event), Event(Event),
} }
/// Fetch job sent to worker thread.
#[derive(Debug, Clone)]
pub struct Fetch {
/// Repo to fetch.
pub repo: Id,
/// Namespaces to fetch.
pub namespaces: Namespaces,
/// Connection we are fetching from
pub remote: NodeId,
/// Indicates whether the fetch request is initiated by a local party
pub initiate: bool,
}
/// 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 { updated: Vec<RefUpdate> },
/// Error fetching the resource from a seed.
Error { from: NodeId, error: FetchError },
}
/// Interface to the network reactor. /// Interface to the network reactor.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Reactor { pub struct Reactor {
@ -61,6 +87,17 @@ impl Reactor {
self.io.push_back(Io::Wakeup(after)); self.io.push_back(Io::Wakeup(after));
} }
pub fn fetch(&mut self, remote: NodeId, repo: Id, namespaces: Namespaces, initiate: bool) {
debug!("Fetch {} from {}..", repo, remote);
self.io.push_back(Io::Fetch(Fetch {
repo,
namespaces,
remote,
initiate,
}));
}
/// Broadcast a message to a list of peers. /// Broadcast a message to a list of peers.
pub fn broadcast<'a>( pub fn broadcast<'a>(
&mut self, &mut self,

View File

@ -1,7 +1,7 @@
use crate::service::message; use crate::service::message;
use crate::service::message::Message; use crate::service::message::Message;
use crate::service::storage; use crate::service::storage;
use crate::service::{Link, LocalTime, NodeId, Reactor, Rng}; use crate::service::{Id, Link, LocalTime, NodeId, Reactor, Rng};
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub enum PingState { pub enum PingState {
@ -14,19 +14,31 @@ pub enum PingState {
Ok, Ok,
} }
#[derive(Debug, Default, Clone)] /// Session protocol.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
/// The default message-based gossip protocol.
#[default]
Gossip,
/// Git smart protocol. Used for fetching repository data.
/// This protocol is used after a connection upgrade via the
/// [`Message::Upgrade`] message.
Git,
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum State { pub enum State {
/// Pre-handshake state. /// Initial state after handshake protocol hand-off.
/// TODO(cloudhead): Remove once noise handshake is implemented. Connected {
#[default] /// Whether this session was initialized with a [`Message::Initialize`].
Initial, initialized: bool,
/// State after successful handshake. /// Connected since this time.
Negotiated {
/// The peer's unique identifier.
id: NodeId,
since: LocalTime, since: LocalTime,
/// Ping state.
ping: PingState, ping: PingState,
/// Session protocol.
protocol: Protocol,
}, },
/// When a peer is disconnected. /// When a peer is disconnected.
Disconnected { since: LocalTime }, Disconnected { since: LocalTime },
@ -77,10 +89,15 @@ pub struct Session {
} }
impl Session { impl Session {
pub fn new(id: NodeId, link: Link, persistent: bool, rng: Rng) -> Self { pub fn new(id: NodeId, link: Link, persistent: bool, rng: Rng, time: LocalTime) -> Self {
Self { Self {
id, id,
state: State::Initial, state: State::Connected {
initialized: false,
since: time,
ping: PingState::default(),
protocol: Protocol::default(),
},
link, link,
subscribe: None, subscribe: None,
persistent, persistent,
@ -90,8 +107,8 @@ impl Session {
} }
} }
pub fn is_negotiated(&self) -> bool { pub fn is_connected(&self) -> bool {
matches!(self.state, State::Negotiated { .. }) matches!(self.state, State::Connected { .. })
} }
pub fn attempts(&self) -> usize { pub fn attempts(&self) -> usize {
@ -102,12 +119,27 @@ impl Session {
self.attempts += 1; self.attempts += 1;
} }
pub fn upgrade(&mut self, repo: Id) -> Option<Message> {
if let State::Connected { protocol, .. } = &mut self.state {
if *protocol == Protocol::Gossip {
*protocol = Protocol::Git;
return Some(Message::Upgrade { repo });
} else {
log::error!(
"Attempted to upgrade protocol for {} which was already upgraded",
self.id
);
}
}
None
}
pub fn connected(&mut self, _link: Link) { pub fn connected(&mut self, _link: Link) {
self.attempts = 0; self.attempts = 0;
} }
pub fn ping(&mut self, reactor: &mut Reactor) -> Result<(), Error> { pub fn ping(&mut self, reactor: &mut Reactor) -> Result<(), Error> {
if let State::Negotiated { ping, .. } = &mut self.state { if let State::Connected { ping, .. } = &mut self.state {
let msg = message::Ping::new(&mut self.rng); let msg = message::Ping::new(&mut self.rng);
*ping = PingState::AwaitingResponse(msg.ponglen); *ping = PingState::AwaitingResponse(msg.ponglen);
@ -115,11 +147,4 @@ impl Session {
} }
Ok(()) Ok(())
} }
pub fn node_id(&self) -> Option<NodeId> {
if let State::Negotiated { id, .. } = &self.state {
return Some(*id);
}
None
}
} }

View File

@ -9,7 +9,7 @@ use crate::address;
use crate::address::Store; use crate::address::Store;
use crate::clock::Timestamp; use crate::clock::Timestamp;
use crate::crypto::test::signer::MockSigner; use crate::crypto::test::signer::MockSigner;
use crate::crypto::Signer; use crate::crypto::{Negotiator, Signer};
use crate::identity::Id; use crate::identity::Id;
use crate::node; use crate::node;
use crate::prelude::*; use crate::prelude::*;
@ -42,7 +42,7 @@ pub struct Peer<S, G> {
impl<S, G> simulator::Peer<S, G> for Peer<S, G> impl<S, G> simulator::Peer<S, G> for Peer<S, G>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + 'static, G: Signer + Negotiator + 'static,
{ {
fn init(&mut self) { fn init(&mut self) {
self.initialize() self.initialize()
@ -103,7 +103,7 @@ impl Default for Config<MockSigner> {
impl<S, G> Peer<S, G> impl<S, G> Peer<S, G>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + 'static, G: Signer + Negotiator + 'static,
{ {
pub fn config( pub fn config(
name: &'static str, name: &'static str,

View File

@ -12,7 +12,7 @@ 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::crypto::Signer; use crate::crypto::{Negotiator, Signer};
use crate::prelude::Address; use crate::prelude::Address;
use crate::service::reactor::Io; use crate::service::reactor::Io;
use crate::service::{DisconnectReason, Event, Message, NodeId}; use crate::service::{DisconnectReason, Event, Message, NodeId};
@ -190,7 +190,7 @@ pub struct Simulation<S, G> {
signer: PhantomData<G>, signer: PhantomData<G>,
} }
impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> { impl<S: WriteStorage + 'static, G: Signer + Negotiator> Simulation<S, G> {
/// 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 {
@ -592,6 +592,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
events.push_back(event); events.push_back(event);
} }
} }
Io::Fetch(..) => todo!("I have no idea what to do here"),
} }
} }

View File

@ -19,6 +19,7 @@ pub enum MessageType {
Subscribe = 8, Subscribe = 8,
Ping = 10, Ping = 10,
Pong = 12, Pong = 12,
Upgrade = 14,
} }
impl From<MessageType> for u16 { impl From<MessageType> for u16 {
@ -39,6 +40,7 @@ impl TryFrom<u16> for MessageType {
8 => Ok(MessageType::Subscribe), 8 => Ok(MessageType::Subscribe),
10 => Ok(MessageType::Ping), 10 => Ok(MessageType::Ping),
12 => Ok(MessageType::Pong), 12 => Ok(MessageType::Pong),
14 => Ok(MessageType::Upgrade),
_ => Err(other), _ => Err(other),
} }
} }
@ -60,6 +62,7 @@ impl Message {
}, },
Self::Ping { .. } => MessageType::Ping, Self::Ping { .. } => MessageType::Ping,
Self::Pong { .. } => MessageType::Pong, Self::Pong { .. } => MessageType::Pong,
Self::Upgrade { .. } => MessageType::Upgrade,
} }
.into() .into()
} }
@ -213,6 +216,9 @@ impl wire::Encode for Message {
Self::Pong { zeroes } => { Self::Pong { zeroes } => {
n += zeroes.encode(writer)?; n += zeroes.encode(writer)?;
} }
Self::Upgrade { repo } => {
n += repo.encode(writer)?;
}
} }
if n > wire::Size::MAX as usize { if n > wire::Size::MAX as usize {
@ -287,6 +293,10 @@ impl wire::Decode for Message {
let zeroes = ZeroBytes::decode(reader)?; let zeroes = ZeroBytes::decode(reader)?;
Ok(Self::Pong { zeroes }) Ok(Self::Pong { zeroes })
} }
Ok(MessageType::Upgrade) => {
let repo = Id::decode(reader)?;
Ok(Self::Upgrade { repo })
}
Err(other) => Err(wire::Error::UnknownMessageType(other)), Err(other) => Err(wire::Error::UnknownMessageType(other)),
} }
} }

View File

@ -6,9 +6,10 @@ use std::collections::VecDeque;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd; use std::os::unix::prelude::RawFd;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Instant;
use std::{io, net}; use std::{io, net};
use crossbeam_channel as chan;
use cyphernet::addr::{Addr as _, HostAddr, PeerAddr}; use cyphernet::addr::{Addr as _, HostAddr, PeerAddr};
use nakamoto_net::{DisconnectReason, Link, LocalTime}; use nakamoto_net::{DisconnectReason, Link, LocalTime};
use netservices::noise::NoiseXk; use netservices::noise::NoiseXk;
@ -21,19 +22,17 @@ use radicle::node::NodeId;
use radicle::storage::WriteStorage; use radicle::storage::WriteStorage;
use crate::crypto::Signer; use crate::crypto::Signer;
use crate::service::reactor::Io; use crate::service::reactor::{Fetch, Io};
use crate::service::{routing, session, Message, Service}; use crate::service::{routing, session, Message, Service};
use crate::worker::{WorkerReq, WorkerResp};
use crate::{address, service}; use crate::{address, service};
/// The peer session type.
pub type Session<N> = NoiseXk<N>;
/// Reactor action. /// Reactor action.
type Action<G> = reactor::Action<NetAccept<Session<G>>, NetTransport<Session<G>, Message>>; type Action<G> = reactor::Action<NetAccept<NoiseXk<G>>, NetTransport<NoiseXk<G>, Message>>;
/// Peer connection state machine. /// Peer connection state machine.
#[derive(Debug)] #[derive(Debug)]
enum Peer { enum Peer<G: Negotiator> {
/// The initial state before handshake is completed. /// The initial state before handshake is completed.
Connecting { link: Link }, Connecting { link: Link },
/// The state after handshake is completed. /// The state after handshake is completed.
@ -45,9 +44,22 @@ enum Peer {
id: NodeId, id: NodeId,
reason: DisconnectReason<service::DisconnectReason>, reason: DisconnectReason<service::DisconnectReason>,
}, },
/// The state after we've started the process of upgraded the peer for a fetch.
/// The request to handover the socket was made to the reactor.
Upgrading {
fetch: Fetch,
link: Link,
id: NodeId,
},
/// The peer is now upgraded and we are in control of the socket.
Upgraded {
link: Link,
id: NodeId,
response: chan::Receiver<WorkerResp<G>>,
},
} }
impl Peer { impl<G: Negotiator> Peer<G> {
/// Return a new connecting peer. /// Return a new connecting peer.
fn connecting(link: Link) -> Self { fn connecting(link: Link) -> Self {
Self::Connecting { link } Self::Connecting { link }
@ -70,18 +82,61 @@ impl Peer {
panic!("Peer::disconnected: session is not connected"); panic!("Peer::disconnected: session is not connected");
} }
} }
/// Switch to upgrading state.
fn upgrading(&mut self, fetch: Fetch) {
if let Self::Connected { id, link } = self {
*self = Self::Upgrading {
fetch,
id: *id,
link: *link,
};
} else {
panic!("Peer::upgrading: session is not connected");
}
}
/// Switch to upgraded state.
fn upgraded(&mut self, listener: chan::Receiver<WorkerResp<G>>) -> Fetch {
if let Self::Upgrading { fetch, id, link } = self {
let fetch = fetch.clone();
*self = Self::Upgraded {
id: *id,
link: *link,
response: listener,
};
fetch
} else {
panic!("Peer::upgraded: can't upgrade before handover");
}
}
/// Switch back from upgraded to connected state.
fn downgrade(&mut self) -> Link {
if let Self::Upgraded { id, link, .. } = self {
let link = *link;
*self = Self::Connected { id: *id, link };
link
} else {
panic!("Peer::downgrade: can't downgrade if not in upgraded state");
}
}
} }
/// Transport protocol implementation for a set of peers. /// Transport protocol implementation for a set of peers.
pub struct Transport<R, S, W, G: Negotiator> { pub struct Transport<R, S, W, G: Negotiator> {
/// Backing service instance. /// Backing service instance.
service: Service<R, S, W, G>, service: Service<R, S, W, G>,
/// Worker pool interface.
worker: chan::Sender<WorkerReq<G>>,
/// Used to performs X25519 key exchange. /// Used to performs X25519 key exchange.
keypair: G, keypair: G,
/// Internal queue of actions to send to the reactor. /// Internal queue of actions to send to the reactor.
actions: VecDeque<Action<G>>, actions: VecDeque<Action<G>>,
/// Peer sessions. /// Peer sessions.
peers: HashMap<RawFd, Peer>, peers: HashMap<RawFd, Peer<G>>,
/// SOCKS5 proxy address. /// SOCKS5 proxy address.
proxy: net::SocketAddr, proxy: net::SocketAddr,
} }
@ -95,6 +150,7 @@ where
{ {
pub fn new( pub fn new(
mut service: Service<R, S, W, G>, mut service: Service<R, S, W, G>,
worker: chan::Sender<WorkerReq<G>>,
keypair: G, keypair: G,
proxy: net::SocketAddr, proxy: net::SocketAddr,
clock: LocalTime, clock: LocalTime,
@ -103,6 +159,7 @@ where
Self { Self {
service, service,
worker,
keypair, keypair,
proxy, proxy,
actions: VecDeque::new(), actions: VecDeque::new(),
@ -141,6 +198,70 @@ where
self.actions.push_back(Action::UnregisterTransport(fd)); self.actions.push_back(Action::UnregisterTransport(fd));
} }
fn upgrade(&mut self, fd: RawFd, fetch: Fetch) {
let Some(peer) = self.peers.get_mut(&fd) else {
log::error!(target: "transport", "Peer with fd {fd} was not found");
return;
};
if let Peer::Disconnected { .. } = peer {
log::error!(target: "transport", "Peer with fd {fd} is already disconnected");
return;
};
log::debug!(target: "transport", "Requesting transport handover from reactor for fd {fd}");
peer.upgrading(fetch);
self.actions.push_back(Action::UnregisterTransport(fd));
}
fn upgraded(&mut self, transport: NetTransport<NoiseXk<G>, Message>) {
let fd = transport.as_raw_fd();
let Some(peer) = self.peers.get_mut(&fd) else {
log::error!(target: "transport", "Peer with fd {fd} was not found");
return;
};
let (send, recv) = chan::bounded::<WorkerResp<G>>(1);
let fetch = peer.upgraded(recv);
// Downgrade the transport to a simple session and the buffer of incoming data that is
// unprocessed. This buffer is provided as initial input to the worker.
let Ok((session, drain)) = transport.downgrade() else {
// This can happen in case the service attempts to send data to a peer after it has
// initiated an upgrade protocol.
panic!("Transport::upgraded: outgoing messages buffer is not empty");
};
if self
.worker
.send(WorkerReq {
fetch,
session,
drain,
channel: send,
})
.is_err()
{
log::error!(target: "transport", "Worker pool is disconnected; cannot send fetch request");
}
}
fn fetch_complete(&mut self, resp: WorkerResp<G>) {
let session = resp.session;
let fd = session.as_raw_fd();
let Some(peer) = self.peers.get_mut(&fd) else {
log::error!(target: "transport", "Peer with fd {fd} was not found");
return;
};
if let Peer::Disconnected { .. } = peer {
log::error!(target: "transport", "Peer with fd {fd} is already disconnected");
return;
};
let link = peer.downgrade();
let transport = NetTransport::upgrade(session, link == Link::Inbound)
.expect("unable to set socket into non-blocking mode");
self.actions.push_back(Action::RegisterTransport(transport));
self.service.fetch_complete(resp.result);
}
} }
impl<R, S, W, G> reactor::Handler for Transport<R, S, W, G> impl<R, S, W, G> reactor::Handler for Transport<R, S, W, G>
@ -150,10 +271,28 @@ where
W: WriteStorage + Send + 'static, W: WriteStorage + Send + 'static,
G: Signer + Negotiator + Send, G: Signer + Negotiator + Send,
{ {
type Listener = NetAccept<Session<G>>; type Listener = NetAccept<NoiseXk<G>>;
type Transport = NetTransport<Session<G>, Message>; type Transport = NetTransport<NoiseXk<G>, Message>;
type Command = service::Command; type Command = service::Command;
fn tick(&mut self, time: Instant) {
// FIXME: Ensure that the time correctly converted.
self.service
.tick(LocalTime::from_secs(time.elapsed().as_secs()));
let mut completed = Vec::new();
for peer in self.peers.values() {
if let Peer::Upgraded { response, .. } = peer {
if let Ok(resp) = response.try_recv() {
completed.push(resp);
}
}
}
for resp in completed {
self.fetch_complete(resp);
}
}
fn handle_wakeup(&mut self) { fn handle_wakeup(&mut self) {
self.service.wake() self.service.wake()
} }
@ -161,11 +300,9 @@ where
fn handle_listener_event( fn handle_listener_event(
&mut self, &mut self,
socket_addr: net::SocketAddr, socket_addr: net::SocketAddr,
event: ListenerEvent<Session<G>>, event: ListenerEvent<NoiseXk<G>>,
duration: Duration, _: Instant,
) { ) {
self.service.tick(LocalTime::from_secs(duration.as_secs()));
match event { match event {
ListenerEvent::Accepted(session) => { ListenerEvent::Accepted(session) => {
log::debug!( log::debug!(
@ -176,7 +313,7 @@ where
self.peers self.peers
.insert(session.as_raw_fd(), Peer::connecting(Link::Inbound)); .insert(session.as_raw_fd(), Peer::connecting(Link::Inbound));
let transport = match NetTransport::<Session<G>, Message>::upgrade(session) { let transport = match NetTransport::<NoiseXk<G>, Message>::upgrade(session, true) {
Ok(transport) => transport, Ok(transport) => transport,
Err(err) => { Err(err) => {
log::error!(target: "transport", "Failed to upgrade accepted peer socket: {err}"); log::error!(target: "transport", "Failed to upgrade accepted peer socket: {err}");
@ -196,11 +333,9 @@ where
fn handle_transport_event( fn handle_transport_event(
&mut self, &mut self,
fd: RawFd, fd: RawFd,
event: SessionEvent<Session<G>, Message>, event: SessionEvent<NoiseXk<G>, Message>,
duration: Duration, _: Instant,
) { ) {
self.service.tick(LocalTime::from_secs(duration.as_secs()));
match event { match event {
SessionEvent::SessionEstablished(node_id) => { SessionEvent::SessionEstablished(node_id) => {
log::debug!(target: "transport", "Session established with {node_id}"); log::debug!(target: "transport", "Session established with {node_id}");
@ -271,7 +406,7 @@ where
} }
fn handle_command(&mut self, cmd: Self::Command) { fn handle_command(&mut self, cmd: Self::Command) {
self.service.command(cmd) self.service.command(cmd);
} }
fn handle_error(&mut self, err: reactor::Error<net::SocketAddr, RawFd>) { fn handle_error(&mut self, err: reactor::Error<net::SocketAddr, RawFd>) {
@ -287,6 +422,9 @@ where
self.actions.push_back(Action::UnregisterTransport(fd)); self.actions.push_back(Action::UnregisterTransport(fd));
} }
reactor::Error::Poll(err) => {
log::error!(target: "transport", "Can't poll connections: {}", err);
}
} }
} }
@ -297,13 +435,22 @@ where
fn handover_transport(&mut self, transport: Self::Transport) { fn handover_transport(&mut self, transport: Self::Transport) {
let fd = transport.as_raw_fd(); let fd = transport.as_raw_fd();
if let Some(Peer::Disconnected { id, reason }) = self.peers.get(&fd) { match self.peers.get(&fd) {
Some(Peer::Disconnected { id, reason }) => {
// Disconnect TCP stream. // Disconnect TCP stream.
drop(transport); drop(transport);
self.service.disconnected(*id, reason); self.service.disconnected(*id, reason);
} else { }
todo!("The handover protocol is not implemented"); Some(Peer::Upgrading { .. }) => {
self.upgraded(transport);
}
Some(_) => {
panic!("Transport::handover_transport: Unexpected peer with fd {fd} handed over from the reactor");
}
None => {
panic!("Transport::handover_transport: Unknown peer with fd {fd} handed over");
}
} }
} }
} }
@ -315,7 +462,7 @@ where
W: WriteStorage + 'static, W: WriteStorage + 'static,
G: Signer + Negotiator, G: Signer + Negotiator,
{ {
type Item = reactor::Action<NetAccept<Session<G>>, NetTransport<Session<G>, Message>>; type Item = reactor::Action<NetAccept<NoiseXk<G>>, NetTransport<NoiseXk<G>, Message>>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some(event) = self.actions.pop_front() { if let Some(event) = self.actions.pop_front() {
@ -352,7 +499,7 @@ where
break; break;
} }
match NetTransport::<Session<G>, Message>::connect( match NetTransport::<NoiseXk<G>, Message>::connect(
PeerAddr::new(node_id, socket_addr), PeerAddr::new(node_id, socket_addr),
&self.keypair, &self.keypair,
) { ) {
@ -376,7 +523,12 @@ where
return self.actions.pop_back(); return self.actions.pop_back();
} }
Io::Wakeup(d) => return Some(reactor::Action::Wakeup(d.into())), Io::Wakeup(d) => return Some(reactor::Action::SetTimer(d.into())),
Io::Fetch(fetch) => {
// TODO: Check that the node_id is connected, queue request otherwise.
let fd = self.by_id(&fetch.remote);
self.upgrade(fd, fetch);
}
} }
} }
None None

View File

@ -0,0 +1,21 @@
use crossbeam_channel as chan;
use netservices::noise::NoiseXk;
use radicle::crypto::Negotiator;
use crate::service::reactor::Fetch;
use crate::service::FetchResult;
/// Worker request.
pub struct WorkerReq<G: Negotiator> {
pub fetch: Fetch,
pub session: NoiseXk<G>,
pub drain: Vec<u8>,
pub channel: chan::Sender<WorkerResp<G>>,
}
/// Worker response.
pub struct WorkerResp<G: Negotiator> {
pub result: FetchResult,
pub session: NoiseXk<G>,
}

View File

@ -27,7 +27,7 @@ pub type BranchName = git::RefString;
pub type Inventory = Vec<Id>; pub type Inventory = Vec<Id>;
/// Describes one or more namespaces. /// Describes one or more namespaces.
#[derive(Default, Debug)] #[derive(Default, Debug, Clone)]
pub enum Namespaces { pub enum Namespaces {
/// All namespaces. /// All namespaces.
#[default] #[default]