node: Integrate new Noise_XK transport

This is a large change that introduces transport encryption via
a Noise_XK handshake. The inner `Service` now works directly
with `NodeId` and doesn't need to interact with socket addresses.

The transport logic takes place in `wire::transport`, and the
established sessions are passed down to `Service`.

Note that the i/o implementation in the `poll-reactor` crate still
needs testing, and there is still work to be done to integrate the
Git transport as part of this architecture.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Dr Maxim Orlovsky 2022-11-29 17:17:55 +01:00 committed by Alexis Sellier
parent 909b99bb7c
commit aa02c52a2f
No known key found for this signature in database
27 changed files with 1014 additions and 1229 deletions

139
Cargo.lock generated
View File

@ -217,6 +217,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.1" version = "0.13.1"
@ -276,6 +282,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [ dependencies = [
"block-padding",
"generic-array", "generic-array",
] ]
@ -288,6 +295,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]] [[package]]
name = "bloomy" name = "bloomy"
version = "1.2.0" version = "1.2.0"
@ -432,6 +445,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.3" version = "0.8.3"
@ -503,6 +522,16 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array",
"subtle",
]
[[package]] [[package]]
name = "ct-codecs" name = "ct-codecs"
version = "1.1.1" version = "1.1.1"
@ -588,11 +617,14 @@ dependencies = [
[[package]] [[package]]
name = "cyphernet" name = "cyphernet"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/cyphernet-wg/rust-cyphernet#762f48e1175f9490c635996c0c1bd07ff1287cfc" source = "git+https://github.com/cyphernet-wg/rust-cyphernet#5568667b76089052a4dfa3dec00d5032b52f505f"
dependencies = [ dependencies = [
"amplify", "amplify",
"ed25519-compact",
"multibase", "multibase",
"serde",
"socks", "socks",
"torut",
] ]
[[package]] [[package]]
@ -638,8 +670,10 @@ version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [ dependencies = [
"convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version",
"syn", "syn",
] ]
@ -709,6 +743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c"
dependencies = [ dependencies = [
"ct-codecs", "ct-codecs",
"ed25519",
"getrandom 0.2.8", "getrandom 0.2.8",
] ]
@ -790,7 +825,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
"sha3", "sha3 0.10.6",
"thiserror", "thiserror",
"uint", "uint",
] ]
@ -1192,6 +1227,16 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@ -1454,7 +1499,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"sha2 0.10.6", "sha2 0.10.6",
"sha3", "sha3 0.10.6",
] ]
[[package]] [[package]]
@ -1637,15 +1682,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "nakamoto-net-poll" name = "netservices"
version = "0.3.0" version = "0.1.0"
source = "git+https://github.com/cloudhead/nakamoto?rev=90cc3eac67aa5cfd5f42cf7cb1e2b155af3214fb#90cc3eac67aa5cfd5f42cf7cb1e2b155af3214fb" source = "git+https://github.com/cyphernet-wg/rust-netservices#045ab140fd33ab3008a0ce8c86d8b9d01b08662c"
dependencies = [ dependencies = [
"crossbeam-channel", "amplify",
"cyphernet",
"libc", "libc",
"log", "poll-reactor",
"nakamoto-net",
"popol",
"socket2", "socket2",
] ]
@ -1972,11 +2016,22 @@ 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 = "0.5.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/Internet2-WG/popol?branch=api#02b8d4089bd234f3d9d46ef27f6e64cfbc45118a"
checksum = "2dac9010e6fc2d991bbf3c362433214c28f7f1a82151b6b1de8dd69d27211495"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -2251,19 +2306,22 @@ dependencies = [
name = "radicle-node" name = "radicle-node"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"amplify",
"anyhow", "anyhow",
"bloomy", "bloomy",
"byteorder", "byteorder",
"chrono", "chrono",
"colored", "colored",
"crossbeam-channel", "crossbeam-channel",
"cyphernet",
"fastrand", "fastrand",
"git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)", "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
"lexopt", "lexopt",
"log", "log",
"nakamoto-net", "nakamoto-net",
"nakamoto-net-poll", "netservices",
"nonempty 0.8.1", "nonempty 0.8.1",
"poll-reactor",
"qcheck", "qcheck",
"qcheck-macros", "qcheck-macros",
"radicle", "radicle",
@ -2464,7 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
dependencies = [ dependencies = [
"crypto-bigint", "crypto-bigint",
"hmac", "hmac 0.12.1",
"zeroize", "zeroize",
] ]
@ -2516,6 +2574,15 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.5" version = "0.36.5"
@ -2587,7 +2654,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d"
dependencies = [ dependencies = [
"hmac", "hmac 0.12.1",
"pbkdf2", "pbkdf2",
"salsa20", "salsa20",
"sha2 0.10.6", "sha2 0.10.6",
@ -2607,6 +2674,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "semver"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.149" version = "1.0.149"
@ -2698,6 +2771,18 @@ dependencies = [
"digest 0.10.6", "digest 0.10.6",
] ]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"keccak",
"opaque-debug",
]
[[package]] [[package]]
name = "sha3" name = "sha3"
version = "0.10.6" version = "0.10.6"
@ -2756,7 +2841,7 @@ dependencies = [
"iri-string", "iri-string",
"k256", "k256",
"rand 0.8.5", "rand 0.8.5",
"sha3", "sha3 0.10.6",
"thiserror", "thiserror",
"time 0.3.17", "time 0.3.17",
] ]
@ -3156,6 +3241,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "torut"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69"
dependencies = [
"base32",
"base64",
"derive_more",
"ed25519-dalek",
"hex",
"hmac 0.11.0",
"rand 0.7.3",
"serde",
"serde_derive",
"sha2 0.9.9",
"sha3 0.9.1",
"tokio",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.4.13" version = "0.4.13"

View File

@ -27,15 +27,18 @@ git = "https://github.com/cloudhead/nakamoto"
rev = "90cc3eac67aa5cfd5f42cf7cb1e2b155af3214fb" rev = "90cc3eac67aa5cfd5f42cf7cb1e2b155af3214fb"
version = "0.3.0" version = "0.3.0"
[patch.crates-io.nakamoto-net-poll]
git = "https://github.com/cloudhead/nakamoto"
rev = "90cc3eac67aa5cfd5f42cf7cb1e2b155af3214fb"
version = "0.3.0"
[patch.crates-io.cyphernet] [patch.crates-io.cyphernet]
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]
git = "https://github.com/cyphernet-wg/rust-netservices"
version = "0.1.0"
[patch.crates-io.netservices]
git = "https://github.com/cyphernet-wg/rust-netservices"
version = "0.1.0"
[patch.crates-io.radicle-git-ext] [patch.crates-io.radicle-git-ext]
git = "https://github.com/radicle-dev/radicle-git" git = "https://github.com/radicle-dev/radicle-git"
rev = "d3115a22158c8395705babefdc89049f7510d32d" rev = "d3115a22158c8395705babefdc89049f7510d32d"

View File

@ -1,43 +0,0 @@
use amplify::{From, Wrapper};
use cyphernet::crypto::{EcPk, EcSig, EcSk, Ecdh};
use ed25519_compact::x25519;
use crate::{PublicKey, SecretKey, Signature};
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref)]
pub struct SharedSecret([u8; 32]);
impl EcPk for PublicKey {}
impl EcSk for SecretKey {
type Pk = PublicKey;
fn to_pk(&self) -> Self::Pk {
self.public_key().into()
}
}
impl Ecdh for SharedSecret {
type Sk = SecretKey;
type Err = ed25519_compact::Error;
fn ecdh(sk: &Self::Sk, pk: &<Self::Sk as EcSk>::Pk) -> Result<Self, Self::Err> {
let xpk = x25519::PublicKey::from_ed25519(&pk.0)?;
let xsk = x25519::SecretKey::from_ed25519(&sk.0)?;
let ss = xpk.dh(&xsk)?;
Ok(Self(*ss))
}
}
impl EcSig for Signature {
type Sk = SecretKey;
fn sign(self, sk: &SecretKey, msg: impl AsRef<[u8]>) -> Self {
sk.0.sign(msg, None).into()
}
fn verify(self, pk: &PublicKey, msg: impl AsRef<[u8]>) -> bool {
pk.0.verify(msg, &self.0).is_ok()
}
}

View File

@ -7,15 +7,11 @@ use thiserror::Error;
pub use ed25519::{Error, KeyPair, Seed}; pub use ed25519::{Error, KeyPair, Seed};
#[cfg(any(test, feature = "test"))]
pub mod test;
pub mod hash; pub mod hash;
#[cfg(feature = "ssh")] #[cfg(feature = "ssh")]
pub mod ssh; pub mod ssh;
#[cfg(any(test, feature = "test"))]
#[cfg(feature = "cyphernet")] pub mod test;
mod cyphernet;
/// Verified (used as type witness). /// Verified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
@ -27,6 +23,19 @@ pub struct Unverified;
/// Output of a Diffie-Hellman key exchange. /// Output of a Diffie-Hellman key exchange.
pub type SharedSecret = [u8; 32]; pub type SharedSecret = [u8; 32];
/// Trait alias used for DiffieHellman key exchange.
#[cfg(feature = "cyphernet")]
pub trait Negotiator:
cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone
{
}
#[cfg(feature = "cyphernet")]
impl<T> Negotiator for T where
T: cyphernet::crypto::Ecdh<Pk = PublicKey, Secret = SharedSecret, Err = Error> + Clone
{
}
/// 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, Error)]
#[error(transparent)] #[error(transparent)]
@ -70,13 +79,6 @@ where
} }
} }
/// A signer that can perform Elliptic-curve DiffieHellman.
pub trait Ecdh: Signer {
/// Perform an ECDH key exchange. Takes the counter-party's public key,
/// and returns a computed shared secret.
fn ecdh(&self, other: &PublicKey) -> Result<SharedSecret, Error>;
}
/// Cryptographic signature. /// Cryptographic signature.
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")] #[serde(into = "String", try_from = "String")]
@ -167,6 +169,9 @@ impl PublicKey {
} }
} }
#[cfg(feature = "cyphernet")]
impl cyphernet::crypto::EcPk for PublicKey {}
/// The private/signing key. /// The private/signing key.
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct SecretKey(ed25519::SecretKey); pub struct SecretKey(ed25519::SecretKey);

View File

@ -5,7 +5,7 @@ use std::{fs, io};
use thiserror::Error; use thiserror::Error;
use zeroize::Zeroizing; use zeroize::Zeroizing;
use crate::{keypair, Ecdh, PublicKey, SecretKey, SharedSecret, Signature, Signer, SignerError}; use crate::{keypair, PublicKey, SecretKey, SharedSecret, Signature, Signer, SignerError};
/// A secret key passphrase. /// A secret key passphrase.
pub type Passphrase = Zeroizing<String>; pub type Passphrase = Zeroizing<String>;
@ -119,7 +119,7 @@ pub enum MemorySignerError {
/// so that signing never fails. /// so that signing never fails.
/// ///
/// Can be created from a [`Keystore`] with the [`MemorySigner::load`] function. /// Can be created from a [`Keystore`] with the [`MemorySigner::load`] function.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct MemorySigner { pub struct MemorySigner {
public: PublicKey, public: PublicKey,
secret: Zeroizing<SecretKey>, secret: Zeroizing<SecretKey>,
@ -139,7 +139,12 @@ impl Signer for MemorySigner {
} }
} }
impl Ecdh for MemorySigner { #[cfg(feature = "cyphernet")]
impl cyphernet::crypto::Ecdh for MemorySigner {
type Pk = PublicKey;
type Secret = SharedSecret;
type Err = ed25519_compact::Error;
fn ecdh(&self, other: &PublicKey) -> Result<SharedSecret, ed25519_compact::Error> { fn ecdh(&self, other: &PublicKey) -> Result<SharedSecret, ed25519_compact::Error> {
let pk = ed25519_compact::x25519::PublicKey::from_ed25519(other)?; let pk = ed25519_compact::x25519::PublicKey::from_ed25519(other)?;
let sk = ed25519_compact::x25519::SecretKey::from_ed25519(&self.secret)?; let sk = ed25519_compact::x25519::SecretKey::from_ed25519(&self.secret)?;

View File

@ -9,19 +9,22 @@ edition = "2021"
test = ["radicle/test", "radicle-crypto/test", "qcheck"] test = ["radicle/test", "radicle-crypto/test", "qcheck"]
[dependencies] [dependencies]
amplify = { version = "4.0.0-beta.1", default-features = false, features = ["std"] }
anyhow = { version = "1" } anyhow = { version = "1" }
bloomy = { version = "1.2" } bloomy = { version = "1.2" }
byteorder = { version = "1" } byteorder = { version = "1" }
chrono = { version = "0.4.0" } chrono = { version = "0.4.0" }
colored = { version = "1.9.0" } colored = { version = "1.9.0" }
crossbeam-channel = { version = "0.5.6" } crossbeam-channel = { version = "0.5.6" }
cyphernet = { version = "0", features = ["serde", "tor", "dns", "ed25519"] }
fastrand = { version = "1.8.0" } fastrand = { version = "1.8.0" }
git-ref-format = { version = "0", features = ["serde", "macro"] } 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" }
nakamoto-net-poll = { version = "0.3.0" } netservices = { version = "0", features = ["poll-reactor", "socket2"] }
nonempty = { version = "0.8.1", features = ["serialize"] } nonempty = { version = "0.8.1", features = ["serialize"] }
poll-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

View File

@ -12,7 +12,7 @@ use crate::clock::Timestamp;
use crate::prelude::Address; use crate::prelude::Address;
use crate::service::NodeId; use crate::service::NodeId;
use crate::sql::transaction; use crate::sql::transaction;
use crate::wire::message::AddressType; use crate::wire::AddressType;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {

View File

@ -1,16 +1,9 @@
use std::{io, net}; use std::io;
use crossbeam_channel as chan;
use nakamoto_net::{LocalTime, Reactor};
use thiserror::Error; use thiserror::Error;
use radicle::crypto::Signer; use crate::address;
use crate::profile::Profile;
use crate::service::{routing, tracking}; use crate::service::{routing, tracking};
use crate::wire::transcode::NoHandshake;
use crate::wire::Wire;
use crate::{address, service};
pub mod handle; pub mod handle;
@ -42,128 +35,3 @@ pub enum Error {
#[error("network error: {0}")] #[error("network error: {0}")]
Net(#[from] nakamoto_net::error::Error), Net(#[from] nakamoto_net::error::Error),
} }
/// Client configuration.
#[derive(Debug, Clone)]
pub struct Config {
/// Client service configuration.
pub service: service::Config,
/// Client listen addresses.
pub listen: Vec<net::SocketAddr>,
}
impl Config {
/// Create a new configuration for the given network.
pub fn new(network: service::Network) -> Self {
Self {
service: service::Config {
network,
..service::Config::default()
},
..Self::default()
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
service: service::Config::default(),
listen: vec![([0, 0, 0, 0], 0).into()],
}
}
}
pub struct Client<R: Reactor> {
reactor: R,
handle: chan::Sender<service::Command>,
commands: chan::Receiver<service::Command>,
shutdown: chan::Sender<()>,
listening: chan::Receiver<net::SocketAddr>,
events: Events,
}
impl<R: Reactor> Client<R> {
pub fn new() -> Result<Self, Error> {
let (handle, commands) = chan::unbounded::<service::Command>();
let (shutdown, shutdown_recv) = chan::bounded(1);
let (listening_send, listening) = chan::bounded(1);
let reactor = R::new(shutdown_recv, listening_send)?;
let events = Events {};
Ok(Self {
reactor,
handle,
commands,
listening,
shutdown,
events,
})
}
pub fn run<G: Signer>(
mut self,
config: Config,
profile: Profile,
signer: G,
) -> Result<(), Error> {
let network = config.service.network;
let rng = fastrand::Rng::new();
let time = LocalTime::now();
let storage = profile.storage;
let node_dir = profile.home.join(NODE_DIR);
let address_db = node_dir.join(ADDRESS_DB_FILE);
let routing_db = node_dir.join(ROUTING_DB_FILE);
let tracking_db = node_dir.join(TRACKING_DB_FILE);
log::info!("Opening address book {}..", address_db.display());
let addresses = address::Book::open(address_db)?;
log::info!("Opening routing table {}..", routing_db.display());
let routing = routing::Table::open(routing_db)?;
log::info!("Opening tracking policy table {}..", tracking_db.display());
let tracking = tracking::Config::open(tracking_db)?;
log::info!("Initializing client ({:?})..", network);
let service = service::Service::new(
config.service,
time,
routing,
storage,
addresses,
tracking,
signer,
rng,
);
self.reactor.run(
&config.listen,
Wire::<_, _, _, _, NoHandshake>::new(service),
self.events,
self.commands,
)?;
Ok(())
}
/// Create a new handle to communicate with the client.
pub fn handle(&self) -> handle::Handle<R::Waker> {
handle::Handle {
waker: self.reactor.waker(),
commands: self.handle.clone(),
shutdown: self.shutdown.clone(),
listening: self.listening.clone(),
}
}
}
pub struct Events {}
impl nakamoto_net::Publisher<service::Event> for Events {
fn publish(&mut self, e: service::Event) {
log::info!("Received event {:?}", e);
}
}

View File

@ -1,8 +1,6 @@
use std::net;
use std::sync::Arc; use std::sync::Arc;
use crossbeam_channel as chan; use crossbeam_channel as chan;
use nakamoto_net::Waker;
use thiserror::Error; use thiserror::Error;
use crate::identity::Id; use crate::identity::Id;
@ -48,62 +46,56 @@ impl<T> From<chan::SendError<T>> for Error {
} }
} }
pub struct Handle<W: Waker> { pub struct Handle {
pub(crate) commands: chan::Sender<service::Command>, pub(crate) controller: reactor::Controller<service::Command>,
pub(crate) shutdown: chan::Sender<()>,
pub(crate) listening: chan::Receiver<net::SocketAddr>,
pub(crate) waker: W,
} }
impl<W: Waker> Handle<W> { impl From<reactor::Controller<service::Command>> for Handle {
fn from(controller: reactor::Controller<service::Command>) -> Handle {
Handle { controller }
}
}
impl Handle {
fn command(&self, cmd: service::Command) -> Result<(), Error> { fn command(&self, cmd: service::Command) -> Result<(), Error> {
self.commands.send(cmd)?; self.controller.send(cmd)?;
self.waker.wake()?;
Ok(()) Ok(())
} }
} }
impl<W: Waker> radicle::node::Handle for Handle<W> { impl radicle::node::Handle for Handle {
type Session = Session; type Session = Session;
type FetchLookup = FetchLookup; type FetchLookup = FetchLookup;
type Error = Error; type Error = Error;
fn listening(&self) -> Result<net::SocketAddr, Error> {
self.listening.recv().map_err(Error::from)
}
fn fetch(&mut self, id: Id) -> Result<Self::FetchLookup, Error> { fn fetch(&mut self, id: Id) -> Result<Self::FetchLookup, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.commands.send(service::Command::Fetch(id, sender))?; self.command(service::Command::Fetch(id, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error> { fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.commands self.command(service::Command::TrackNode(id, alias, sender))?;
.send(service::Command::TrackNode(id, alias, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Error> { fn untrack_node(&mut self, id: NodeId) -> Result<bool, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.commands self.command(service::Command::UntrackNode(id, sender))?;
.send(service::Command::UntrackNode(id, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }
fn track_repo(&mut self, id: Id) -> Result<bool, Error> { fn track_repo(&mut self, id: Id) -> Result<bool, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.commands self.command(service::Command::TrackRepo(id, sender))?;
.send(service::Command::TrackRepo(id, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }
fn untrack_repo(&mut self, id: Id) -> Result<bool, Error> { fn untrack_repo(&mut self, id: Id) -> Result<bool, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.commands self.command(service::Command::UntrackRepo(id, sender))?;
.send(service::Command::UntrackRepo(id, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }
@ -151,9 +143,6 @@ impl<W: Waker> radicle::node::Handle for Handle<W> {
} }
fn shutdown(self) -> Result<(), Error> { fn shutdown(self) -> Result<(), Error> {
self.shutdown.send(())?; self.controller.shutdown().map_err(|_| Error::NotConnected)
self.waker.wake()?;
Ok(())
} }
} }

View File

@ -1,3 +1,6 @@
#[macro_use]
extern crate amplify;
pub mod address; pub mod address;
pub mod bounded; pub mod bounded;
pub mod client; pub mod client;

View File

@ -1,22 +1,27 @@
use std::{env, net, process, thread}; use std::{env, net, process, thread};
use anyhow::Context as _; use anyhow::Context as _;
use cyphernet::addr::PeerAddr;
use nakamoto_net::LocalDuration; use nakamoto_net::{LocalDuration, LocalTime};
use reactor::poller::popol;
use reactor::Reactor;
use radicle::profile; use radicle::profile;
use radicle_node::client::handle::Handle;
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::logger; use radicle_node::prelude::{Address, NodeId};
use radicle_node::prelude::Address; use radicle_node::service::{routing, tracking};
use radicle_node::{client, control, service}; use radicle_node::wire::Transport;
use radicle_node::{address, control, logger, service};
type Reactor = nakamoto_net_poll::Reactor<net::TcpStream>;
#[derive(Debug)] #[derive(Debug)]
struct Options { struct Options {
connect: Vec<Address>, connect: Vec<(NodeId, Address)>,
external_addresses: Vec<Address>, external_addresses: Vec<Address>,
limits: service::config::Limits, limits: service::config::Limits,
// FIXME(cloudhead): Listen on incoming connections.
#[allow(dead_code)]
listen: Vec<net::SocketAddr>, listen: Vec<net::SocketAddr>,
} }
@ -33,8 +38,8 @@ impl Options {
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
Long("connect") => { Long("connect") => {
let addr = parser.value()?.parse()?; let peer: PeerAddr<NodeId, Address> = parser.value()?.parse()?;
connect.push(addr); connect.push((*peer.id(), peer.addr().clone()));
} }
Long("external-address") => { Long("external-address") => {
let addr = parser.value()?.parse()?; let addr = parser.value()?.parse()?;
@ -81,33 +86,48 @@ fn main() -> anyhow::Result<()> {
let options = Options::from_env()?; let options = Options::from_env()?;
let profile = radicle::Profile::load().context("Failed to load node profile")?; let profile = radicle::Profile::load().context("Failed to load node profile")?;
let node = profile.node(); let node = profile.node();
let client = client::Client::<Reactor>::new().context("Failed to initialize client")?; let passphrase = env::var(profile::env::RAD_PASSPHRASE)
let signer = match profile.signer() { .context("`RAD_PASSPHRASE` is required to be set for the node to establish connections")?
Ok(signer) => signer.boxed(), .into();
Err(err) => { let signer = MemorySigner::load(&profile.keystore, passphrase)?;
let passphrase = env::var(profile::env::RAD_PASSPHRASE) let negotiator = signer.clone();
.context("Either ssh-agent must be initialized, or `RAD_PASSPHRASE` must be set") let config = service::Config {
.context(err)? connect: options.connect.into_iter().collect(),
.into(); external_addresses: options.external_addresses,
MemorySigner::load(&profile.keystore, passphrase)?.boxed() limits: options.limits,
} ..service::Config::default()
};
let handle = client.handle();
let config = client::Config {
service: service::Config {
connect: options.connect,
external_addresses: options.external_addresses,
limits: options.limits,
..service::Config::default()
},
listen: options.listen,
}; };
let proxy_addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
let network = config.network;
let rng = fastrand::Rng::new();
let clock = LocalTime::now();
let storage = profile.storage;
let node_dir = profile.home.join(NODE_DIR);
let address_db = node_dir.join(ADDRESS_DB_FILE);
let routing_db = node_dir.join(ROUTING_DB_FILE);
let tracking_db = node_dir.join(TRACKING_DB_FILE);
let t1 = thread::spawn(move || control::listen(node, handle)); log::info!("Opening address book {}..", address_db.display());
let t2 = thread::spawn(move || client.run(config, profile, signer)); let addresses = address::Book::open(address_db)?;
t1.join().unwrap()?; log::info!("Opening routing table {}..", routing_db.display());
t2.join().unwrap()?; let routing = routing::Table::open(routing_db)?;
log::info!("Opening tracking policy table {}..", tracking_db.display());
let tracking = tracking::Config::open(tracking_db)?;
log::info!("Initializing service ({:?})..", network);
let service = service::Service::new(
config, clock, routing, storage, addresses, tracking, signer, rng,
);
let wire = Transport::new(service, negotiator, proxy_addr, clock);
let reactor = Reactor::new(wire, popol::Poller::new());
let handle = Handle::from(reactor.controller());
let control = thread::spawn(move || control::listen(node, handle));
control.join().unwrap()?;
reactor.join().unwrap();
Ok(()) Ok(())
} }

View File

@ -48,8 +48,6 @@ use self::reactor::Reactor;
/// Default radicle protocol port. /// Default radicle protocol port.
pub const DEFAULT_PORT: u16 = 8776; pub const DEFAULT_PORT: u16 = 8776;
/// Protocol version. Only updated for wire protocol changes.
pub const PROTOCOL_VERSION: u32 = 1;
/// Target number of peers to maintain connections to. /// Target number of peers to maintain connections to.
pub const TARGET_OUTBOUND_PEERS: usize = 8; pub const TARGET_OUTBOUND_PEERS: usize = 8;
/// How often to run the "idle" task. /// How often to run the "idle" task.
@ -113,7 +111,7 @@ pub enum FetchError {
pub enum FetchLookup { pub enum FetchLookup {
/// Found seeds for the given project. /// Found seeds for the given project.
Found { Found {
seeds: NonEmpty<net::SocketAddr>, seeds: NonEmpty<NodeId>,
results: chan::Receiver<FetchResult>, results: chan::Receiver<FetchResult>,
}, },
/// Can't fetch because no seeds were found for this project. /// Can't fetch because no seeds were found for this project.
@ -130,14 +128,11 @@ pub enum FetchLookup {
pub enum FetchResult { pub enum FetchResult {
/// Successful fetch from a seed. /// Successful fetch from a seed.
Fetched { Fetched {
from: net::SocketAddr, from: NodeId,
updated: Vec<RefUpdate>, updated: Vec<RefUpdate>,
}, },
/// Error fetching the resource from a seed. /// Error fetching the resource from a seed.
Error { Error { from: NodeId, error: FetchError },
from: net::SocketAddr,
error: FetchError,
},
} }
/// Function used to query internal service state. /// Function used to query internal service state.
@ -148,7 +143,7 @@ pub enum Command {
/// Announce repository references for given project id to peers. /// Announce repository references for given project id to peers.
AnnounceRefs(Id), AnnounceRefs(Id),
/// Connect to node with the given address. /// Connect to node with the given address.
Connect(net::SocketAddr), Connect(NodeId, Address),
/// Fetch the given project from the network. /// Fetch the given project from the network.
Fetch(Id, chan::Sender<FetchLookup>), Fetch(Id, chan::Sender<FetchLookup>),
/// Track the given project. /// Track the given project.
@ -167,7 +162,7 @@ impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({})", id), Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({})", id),
Self::Connect(addr) => write!(f, "Connect({})", addr), Self::Connect(id, addr) => write!(f, "Connect({}, {})", id, addr),
Self::Fetch(id, _) => write!(f, "Fetch({})", id), Self::Fetch(id, _) => write!(f, "Fetch({})", id),
Self::TrackRepo(id, _) => write!(f, "TrackRepo({})", id), Self::TrackRepo(id, _) => write!(f, "TrackRepo({})", id),
Self::UntrackRepo(id, _) => write!(f, "UntrackRepo({})", id), Self::UntrackRepo(id, _) => write!(f, "UntrackRepo({})", id),
@ -384,8 +379,8 @@ where
// Connect to configured peers. // Connect to configured peers.
let addrs = self.config.connect.clone(); let addrs = self.config.connect.clone();
for addr in addrs { for (id, addr) in addrs {
self.reactor.connect(addr); self.reactor.connect(id, addr);
} }
} }
@ -440,7 +435,7 @@ where
debug!("Command {:?}", cmd); debug!("Command {:?}", cmd);
match cmd { match cmd {
Command::Connect(addr) => self.reactor.connect(addr), Command::Connect(id, addr) => self.reactor.connect(id, addr),
Command::Fetch(id, resp) => { Command::Fetch(id, resp) => {
if !self if !self
.tracking .tracking
@ -472,7 +467,7 @@ where
let (results_, results) = chan::bounded(seeds.len()); let (results_, results) = chan::bounded(seeds.len());
resp.send(FetchLookup::Found { resp.send(FetchLookup::Found {
seeds: seeds.clone().map(|(_, peer)| peer.addr), seeds: seeds.clone().map(|(_, peer)| peer.id),
results, results,
}) })
.ok(); .ok();
@ -483,7 +478,7 @@ where
Ok(updated) => { Ok(updated) => {
results_ results_
.send(FetchResult::Fetched { .send(FetchResult::Fetched {
from: peer.addr, from: peer.id,
updated, updated,
}) })
.ok(); .ok();
@ -491,7 +486,7 @@ where
Err(err) => { Err(err) => {
results_ results_
.send(FetchResult::Error { .send(FetchResult::Error {
from: peer.addr, from: peer.id,
error: err.into(), error: err.into(),
}) })
.ok(); .ok();
@ -536,38 +531,31 @@ where
} }
} }
pub fn attempted(&mut self, addr: &net::SocketAddr) { pub fn accepted(&mut self, _addr: net::SocketAddr) {
let address = Address::from(*addr); // Inbound connection attempt.
let persistent = self.config.is_persistent(&address); }
pub fn attempted(&mut self, id: NodeId, _addr: &Address) {
let persistent = self.config.is_persistent(&id);
let peer = self let peer = self
.sessions .sessions
.entry(*addr) .entry(id)
.or_insert_with(|| Session::new(*addr, Link::Outbound, persistent, self.rng.clone())); .or_insert_with(|| Session::new(id, Link::Outbound, persistent, self.rng.clone()));
peer.attempted(); peer.attempted();
} }
pub fn connecting( pub fn connected(&mut self, remote: NodeId, link: Link) {
&mut self, debug!("Connected to {} ({:?})", remote, link);
_addr: net::SocketAddr,
_local_addr: &net::SocketAddr,
_link: Link,
) {
}
pub fn connected(&mut self, addr: net::SocketAddr, link: Link) {
let address = addr.into();
debug!("Connected to {} ({:?})", addr, link);
// For outbound connections, we are the first to say "Hello". // For outbound connections, we are the first to say "Hello".
// 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() {
if let Some(peer) = self.sessions.get_mut(&addr) { if let Some(peer) = self.sessions.get_mut(&remote) {
if link.is_outbound() { if link.is_outbound() {
self.reactor.write_all( self.reactor.write_all(
addr, remote,
gossip::handshake( gossip::handshake(
self.clock.as_secs(), self.clock.as_secs(),
&self.storage, &self.storage,
@ -581,11 +569,11 @@ where
} }
} else { } else {
self.sessions.insert( self.sessions.insert(
addr, remote,
Session::new( Session::new(
addr, remote,
Link::Inbound, Link::Inbound,
self.config.is_persistent(&address), self.config.is_persistent(&remote),
self.rng.clone(), self.rng.clone(),
), ),
); );
@ -594,55 +582,57 @@ where
pub fn disconnected( pub fn disconnected(
&mut self, &mut self,
addr: &net::SocketAddr, remote: NodeId,
reason: &nakamoto::DisconnectReason<DisconnectReason>, reason: &nakamoto::DisconnectReason<DisconnectReason>,
) { ) {
let since = self.local_time(); let since = self.local_time();
let address = Address::from(*addr);
debug!("Disconnected from {} ({})", addr, reason); debug!("Disconnected from {} ({})", remote, reason);
if let Some(session) = self.sessions.get_mut(addr) { if let Some(session) = self.sessions.get_mut(&remote) {
session.state = session::State::Disconnected { since }; session.state = session::State::Disconnected { since };
// Attempt to re-connect to persistent peers. // Attempt to re-connect to persistent peers.
if self.config.is_persistent(&address) && session.attempts() < MAX_CONNECTION_ATTEMPTS { if let Some(address) = self.config.peer(&remote) {
if reason.is_dial_err() { if session.attempts() < MAX_CONNECTION_ATTEMPTS {
return; if reason.is_dial_err() {
}
if let nakamoto::DisconnectReason::Protocol(r) = reason {
if !r.is_transient() {
return; return;
} }
if let nakamoto::DisconnectReason::Protocol(r) = reason {
if !r.is_transient() {
return;
}
}
// TODO: Eventually we want a delay before attempting a reconnection,
// with exponential back-off.
debug!(
"Reconnecting to {} (attempts={})...",
remote,
session.attempts()
);
// TODO: Try to reconnect only if the peer was attempted. A disconnect without
// even a successful attempt means that we're unlikely to be able to reconnect.
self.reactor.connect(remote, address.clone());
} }
// TODO: Eventually we want a delay before attempting a reconnection,
// with exponential back-off.
debug!(
"Reconnecting to {} (attempts={})...",
addr,
session.attempts()
);
// TODO: Try to reconnect only if the peer was attempted. A disconnect without
// even a successful attempt means that we're unlikely to be able to reconnect.
self.reactor.connect(*addr);
} else { } else {
self.sessions.remove(addr); self.sessions.remove(&remote);
self.maintain_connections(); self.maintain_connections();
} }
} }
} }
pub fn received_message(&mut self, addr: &net::SocketAddr, message: Message) { pub fn received_message(&mut self, remote: NodeId, message: Message) {
match self.handle_message(addr, message) { match self.handle_message(&remote, message) {
Err(session::Error::NotFound(ip)) => { Err(session::Error::NotFound(id)) => {
error!("Session not found for {ip}"); error!("Session not found for {id}");
} }
Err(err) => { Err(err) => {
// If there's an error, stop processing messages from this peer. // If there's an error, stop processing messages from this peer.
// However, we still relay messages returned up to this point. // However, we still relay messages returned up to this point.
self.reactor.disconnect(*addr, DisconnectReason::Error(err)); self.reactor
.disconnect(remote, DisconnectReason::Error(err));
// FIXME: The peer should be set in a state such that we don'that // FIXME: The peer should be set in a state such that we don'that
// process further messages. // process further messages.
@ -822,7 +812,7 @@ where
pub fn handle_message( pub fn handle_message(
&mut self, &mut self,
remote: &net::SocketAddr, remote: &NodeId,
message: Message, message: Message,
) -> Result<(), session::Error> { ) -> Result<(), session::Error> {
let Some(peer) = self.sessions.get_mut(remote) else { let Some(peer) = self.sessions.get_mut(remote) else {
@ -830,18 +820,15 @@ where
}; };
peer.last_active = self.clock; peer.last_active = self.clock;
debug!("Received {:?} from {}", &message, peer.ip()); debug!("Received {:?} from {}", &message, peer.id);
match (&mut peer.state, message) { match (&mut peer.state, message) {
(session::State::Initial, Message::Initialize { id, version, addrs }) => { (session::State::Initial, Message::Initialize {}) => {
if version != PROTOCOL_VERSION {
return Err(session::Error::WrongVersion(version));
}
// Nb. This is a very primitive handshake. Eventually we should have anyhow // Nb. This is a very primitive handshake. Eventually we should have anyhow
// extra "acknowledgment" message sent when the `Initialize` is well received. // extra "acknowledgment" message sent when the `Initialize` is well received.
if peer.link.is_inbound() { if peer.link.is_inbound() {
self.reactor.write_all( self.reactor.write_all(
peer.addr, peer.id,
gossip::handshake( gossip::handshake(
self.clock.as_secs(), self.clock.as_secs(),
&self.storage, &self.storage,
@ -855,16 +842,15 @@ where
// 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 { peer.state = session::State::Negotiated {
id, id: *remote,
since: self.clock, since: self.clock,
addrs: addrs.unbound(),
ping: Default::default(), ping: Default::default(),
}; };
} }
(session::State::Initial, _) => { (session::State::Initial, _) => {
debug!( debug!(
"Disconnecting peer {} for sending us a message before handshake", "Disconnecting peer {} for sending us a message before handshake",
peer.ip() peer.id
); );
return Err(session::Error::Misbehavior); return Err(session::Error::Misbehavior);
} }
@ -882,10 +868,9 @@ where
let relay_to = self let relay_to = self
.sessions .sessions
.negotiated() .negotiated()
.filter(|(addr, _, _)| *addr != remote) .filter(|(id, _)| *id != remote && *id != &ann.node);
.filter(|(_, id, _)| **id != ann.node);
self.reactor.relay(ann.clone(), relay_to.map(|(_, _, p)| p)); self.reactor.relay(ann.clone(), relay_to.map(|(_, p)| p));
return Ok(()); return Ok(());
} }
@ -895,14 +880,14 @@ where
.gossip .gossip
.filtered(&subscribe.filter, subscribe.since, subscribe.until) .filtered(&subscribe.filter, subscribe.since, subscribe.until)
{ {
self.reactor.write(peer.addr, msg); self.reactor.write(peer.id, msg);
} }
peer.subscribe = Some(subscribe); peer.subscribe = Some(subscribe);
} }
(session::State::Negotiated { .. }, Message::Initialize { .. }) => { (session::State::Negotiated { .. }, Message::Initialize { .. }) => {
debug!( debug!(
"Disconnecting peer {} for sending us a redundant handshake message", "Disconnecting peer {} for sending us a redundant handshake message",
peer.ip() peer.id
); );
return Err(session::Error::Misbehavior); return Err(session::Error::Misbehavior);
} }
@ -912,7 +897,7 @@ where
return Ok(()); return Ok(());
} }
self.reactor.write( self.reactor.write(
peer.addr, peer.id,
Message::Pong { Message::Pong {
zeroes: ZeroBytes::new(ponglen), zeroes: ZeroBytes::new(ponglen),
}, },
@ -926,7 +911,7 @@ where
} }
} }
(session::State::Disconnected { .. }, msg) => { (session::State::Disconnected { .. }, msg) => {
debug!("Ignoring {:?} from disconnected peer {}", msg, peer.ip()); debug!("Ignoring {:?} from disconnected peer {}", msg, peer.id);
} }
} }
Ok(()) Ok(())
@ -966,7 +951,7 @@ where
let node = self.node_id(); let node = self.node_id();
let repo = self.storage.repository(id)?; let repo = self.storage.repository(id)?;
let remote = repo.remote(&node)?; let remote = repo.remote(&node)?;
let peers = self.sessions.negotiated().map(|(_, _, p)| p); let peers = self.sessions.negotiated().map(|(_, p)| p);
let timestamp = self.clock.as_secs(); let timestamp = self.clock.as_secs();
if remote.refs.len() > Refs::max() { if remote.refs.len() > Refs::max() {
@ -976,7 +961,6 @@ where
); );
} }
let refs = BoundedVec::collect_from(&mut remote.refs.iter().map(|(a, b)| (a.clone(), *b))); let refs = BoundedVec::collect_from(&mut remote.refs.iter().map(|(a, b)| (a.clone(), *b)));
let msg = AnnouncementMessage::from(RefsAnnouncement { let msg = AnnouncementMessage::from(RefsAnnouncement {
id, id,
refs, refs,
@ -1001,8 +985,8 @@ where
&self.signer, &self.signer,
); );
for addr in self.sessions.negotiated().map(|(_, _, p)| p.addr) { for id in self.sessions.negotiated().map(|(id, _)| id) {
self.reactor.write(addr, inv.clone()); self.reactor.write(*id, inv.clone());
} }
Ok(()) Ok(())
} }
@ -1025,12 +1009,11 @@ where
let stale = self let stale = self
.sessions .sessions
.negotiated() .negotiated()
.filter(|(_, _, session)| session.last_active < *now - STALE_CONNECTION_TIMEOUT); .filter(|(_, session)| session.last_active < *now - STALE_CONNECTION_TIMEOUT);
for (_, _, session) in stale {
self.reactor.disconnect( for (_, session) in stale {
session.addr, self.reactor
DisconnectReason::Error(session::Error::Timeout), .disconnect(session.id, DisconnectReason::Error(session::Error::Timeout));
);
} }
} }
@ -1046,16 +1029,18 @@ where
} }
} }
fn choose_addresses(&mut self) -> Vec<Address> { fn choose_addresses(&mut self) -> Vec<(NodeId, Address)> {
let mut initializing: Vec<Address> = Vec::new(); // TODO(cloudhead): Remove once noise is implemented.
let mut initializing: Vec<NodeId> = Vec::new();
let mut negotiated: HashMap<NodeId, &Session> = HashMap::new(); let mut negotiated: HashMap<NodeId, &Session> = HashMap::new();
for s in self.sessions.values() { for s in self.sessions.values() {
if !s.link.is_outbound() { if !s.link.is_outbound() {
continue; continue;
} }
match s.state { match s.state {
// TODO(cloudhead): Remove when we have noise handshake.
session::State::Initial => { session::State::Initial => {
initializing.push(s.addr.into()); initializing.push(s.id);
} }
session::State::Negotiated { id, .. } => { session::State::Negotiated { id, .. } => {
negotiated.insert(id, s); negotiated.insert(id, s);
@ -1074,11 +1059,11 @@ where
self.addresses self.addresses
.entries() .entries()
.unwrap() .unwrap()
.filter(|(node_id, s)| { .filter(|(node_id, _)| {
!initializing.contains(&s.addr) && !negotiated.contains_key(node_id) !initializing.contains(node_id) && !negotiated.contains_key(node_id)
}) })
.take(wanted) .take(wanted)
.map(|(_, s)| s.addr) .map(|(n, s)| (n, s.addr))
.collect() .collect()
} }
@ -1087,8 +1072,8 @@ where
if addrs.is_empty() { if addrs.is_empty() {
debug!("No eligible peers available to connect to"); debug!("No eligible peers available to connect to");
} }
for addr in addrs { for (id, addr) in addrs {
self.reactor.connect(addr.clone()); self.reactor.connect(id, addr.clone());
} }
} }
} }
@ -1149,6 +1134,7 @@ where
#[derive(Debug)] #[derive(Debug)]
pub enum DisconnectReason { pub enum DisconnectReason {
User, User,
Peer,
Error(session::Error), Error(session::Error),
} }
@ -1156,6 +1142,7 @@ impl DisconnectReason {
fn is_transient(&self) -> bool { fn is_transient(&self) -> bool {
match self { match self {
Self::User => false, Self::User => false,
Self::Peer => false,
Self::Error(..) => false, Self::Error(..) => false,
} }
} }
@ -1171,6 +1158,7 @@ impl fmt::Display for DisconnectReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::User => write!(f, "user"), Self::User => write!(f, "user"),
Self::Peer => write!(f, "peer"),
Self::Error(err) => write!(f, "error: {}", err), Self::Error(err) => write!(f, "error: {}", err),
} }
} }
@ -1258,7 +1246,7 @@ impl Node {
#[derive(Debug)] #[derive(Debug)]
/// Holds currently (or recently) connected peers. /// Holds currently (or recently) connected peers.
pub struct Sessions(AddressBook<net::SocketAddr, Session>); pub struct Sessions(AddressBook<NodeId, Session>);
impl Sessions { impl Sessions {
pub fn new(rng: Rng) -> Self { pub fn new(rng: Rng) -> Self {
@ -1270,25 +1258,23 @@ impl Sessions {
} }
/// Iterator over fully negotiated peers. /// Iterator over fully negotiated peers.
pub fn negotiated( pub fn negotiated(&self) -> impl Iterator<Item = (&NodeId, &Session)> + Clone {
&self,
) -> impl Iterator<Item = (&net::SocketAddr, &NodeId, &Session)> + Clone {
self.0 self.0
.iter() .iter()
.filter_map(move |(addr, sess)| match &sess.state { .filter_map(move |(_, sess)| match &sess.state {
session::State::Negotiated { id, .. } => Some((addr, id, sess)), session::State::Negotiated { id, .. } => 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 = (&net::SocketAddr, &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_negotiated())
} }
} }
impl Deref for Sessions { impl Deref for Sessions {
type Target = AddressBook<net::SocketAddr, Session>; type Target = AddressBook<NodeId, Session>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -1350,14 +1336,7 @@ mod gossip {
}; };
let mut msgs = vec![ let mut msgs = vec![
Message::init( Message::init(),
*signer.public_key(),
config
.external_addresses
.clone()
.try_into()
.expect("external addresses are within the limit"),
),
Message::inventory(gossip::inventory(timestamp, inventory), signer), Message::inventory(gossip::inventory(timestamp, inventory), signer),
Message::subscribe(filter, timestamp, Timestamp::MAX), Message::subscribe(filter, timestamp, Timestamp::MAX),
]; ];

View File

@ -1,6 +1,7 @@
use super::nakamoto::LocalDuration; use super::nakamoto::LocalDuration;
use crate::service::message::Address; use crate::service::message::Address;
use crate::service::NodeId;
/// Peer-to-peer network. /// Peer-to-peer network.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@ -33,7 +34,7 @@ impl Default for Limits {
pub struct Config { pub struct Config {
/// Peers to connect to on startup. /// Peers to connect to on startup.
/// Connections to these peers will be maintained. /// Connections to these peers will be maintained.
pub connect: Vec<Address>, pub connect: Vec<(NodeId, Address)>,
/// Specify the node's public addresses /// Specify the node's public addresses
pub external_addresses: Vec<Address>, pub external_addresses: Vec<Address>,
/// Peer-to-peer network. /// Peer-to-peer network.
@ -50,7 +51,7 @@ impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
connect: Vec::default(), connect: Vec::default(),
external_addresses: Vec::default(), external_addresses: vec![],
network: Network::default(), network: Network::default(),
relay: true, relay: true,
listen: vec![], listen: vec![],
@ -67,8 +68,12 @@ impl Config {
} }
} }
pub fn is_persistent(&self, addr: &Address) -> bool { pub fn peer(&self, id: &NodeId) -> Option<&Address> {
self.connect.contains(addr) self.connect.iter().find(|(i, _)| i == id).map(|(_, a)| a)
}
pub fn is_persistent(&self, id: &NodeId) -> bool {
self.connect.iter().any(|(i, _)| i == id)
} }
pub fn alias(&self) -> [u8; 32] { pub fn alias(&self) -> [u8; 32] {

View File

@ -1,7 +1,5 @@
use std::str::FromStr; use cyphernet::addr::{HostAddr, NetAddr};
use std::{fmt, io, mem, net}; use std::{fmt, io, mem, net, ops};
use thiserror::Error;
use crate::crypto; use crate::crypto;
use crate::git; use crate::git;
@ -9,7 +7,7 @@ use crate::identity::Id;
use crate::node; use crate::node;
use crate::prelude::BoundedVec; use crate::prelude::BoundedVec;
use crate::service::filter::Filter; use crate::service::filter::Filter;
use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION}; use crate::service::{NodeId, Timestamp, DEFAULT_PORT};
use crate::wire; use crate::wire;
/// Maximum number of addresses which can be announced to other nodes. /// Maximum number of addresses which can be announced to other nodes.
@ -30,83 +28,30 @@ impl fmt::Display for Hostname {
} }
/// Peer public protocol address. /// Peer public protocol address.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Wrapper, Clone, Eq, PartialEq, Debug, From)]
pub enum Address { #[wrapper(Display, FromStr)]
Ipv4 { pub struct Address(NetAddr<DEFAULT_PORT>);
ip: net::Ipv4Addr,
port: u16, impl cyphernet::addr::Addr for Address {
}, fn port(&self) -> u16 {
Ipv6 { self.0.port()
ip: net::Ipv6Addr, }
port: u16, }
},
Hostname { impl ops::Deref for Address {
host: Hostname, type Target = NetAddr<DEFAULT_PORT>;
port: u16,
}, fn deref(&self) -> &Self::Target {
/// Tor V3 onion address. &self.0
Onion { }
key: crypto::PublicKey,
port: u16,
checksum: u16,
version: u8,
},
} }
impl From<net::SocketAddr> for Address { impl From<net::SocketAddr> for Address {
fn from(other: net::SocketAddr) -> Self { fn from(addr: net::SocketAddr) -> Self {
let port = other.port(); Address(NetAddr {
host: HostAddr::Ip(addr.ip()),
match other.ip() { port: Some(addr.port()),
net::IpAddr::V4(ip) => Self::Ipv4 { ip, port }, })
net::IpAddr::V6(ip) => Self::Ipv6 { ip, port },
}
}
}
#[derive(Debug, Error)]
pub enum AddressParseError {
#[error("unsupported address type `{0}`")]
Unsupported(String),
}
impl FromStr for Address {
type Err = AddressParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(addr) = net::SocketAddr::from_str(s) {
match addr.ip() {
net::IpAddr::V4(ip) => Ok(Self::Ipv4 {
ip,
port: addr.port(),
}),
net::IpAddr::V6(ip) => Ok(Self::Ipv6 {
ip,
port: addr.port(),
}),
}
} else {
Err(Self::Err::Unsupported(s.to_owned()))
}
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ipv4 { ip, port } => {
write!(f, "{}:{}", ip, port)
}
Self::Ipv6 { ip, port } => {
write!(f, "{}:{}", ip, port)
}
Self::Hostname { host, port } => {
write!(f, "{}:{}", host, port)
}
Self::Onion { key, port, .. } => {
write!(f, "{}:{}", key, port)
}
}
} }
} }
@ -366,12 +311,7 @@ impl Announcement {
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub enum Message { pub enum Message {
/// The first message sent to a peer after connection. /// The first message sent to a peer after connection.
Initialize { Initialize {},
// TODO: This is currently untrusted.
id: NodeId,
version: u32,
addrs: BoundedVec<Address, ADDRESS_LIMIT>,
},
/// Subscribe to gossip messages matching the filter and time range. /// Subscribe to gossip messages matching the filter and time range.
Subscribe(Subscribe), Subscribe(Subscribe),
@ -394,12 +334,8 @@ pub enum Message {
} }
impl Message { impl Message {
pub fn init(id: NodeId, addrs: BoundedVec<Address, ADDRESS_LIMIT>) -> Self { pub fn init() -> Self {
Self::Initialize { Self::Initialize {}
id,
version: PROTOCOL_VERSION,
addrs,
}
} }
pub fn announcement( pub fn announcement(
@ -471,7 +407,7 @@ impl From<Announcement> for Message {
impl fmt::Debug for Message { impl fmt::Debug for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Initialize { id, .. } => write!(f, "Initialize({})", id), Self::Initialize { .. } => write!(f, "Initialize(..)"),
Self::Subscribe(Subscribe { since, until, .. }) => { Self::Subscribe(Subscribe { since, until, .. }) => {
write!(f, "Subscribe({}..{})", since, until) write!(f, "Subscribe({}..{})", since, until)
} }

View File

@ -1,5 +1,4 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::net;
use log::*; use log::*;
@ -12,11 +11,11 @@ use super::message::{Announcement, AnnouncementMessage};
#[derive(Debug)] #[derive(Debug)]
pub enum Io { pub enum Io {
/// There are some messages ready to be sent to a peer. /// There are some messages ready to be sent to a peer.
Write(net::SocketAddr, Vec<Message>), Write(NodeId, Vec<Message>),
/// Connect to a peer. /// Connect to a peer.
Connect(net::SocketAddr), Connect(NodeId, Address),
/// Disconnect from a peer. /// Disconnect from a peer.
Disconnect(net::SocketAddr, DisconnectReason), Disconnect(NodeId, DisconnectReason),
/// 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.
@ -37,35 +36,23 @@ impl Reactor {
} }
/// Connect to a peer. /// Connect to a peer.
pub fn connect(&mut self, addr: impl Into<Address>) { pub fn connect(&mut self, id: NodeId, addr: Address) {
// TODO: Make sure we don't try to connect more than once to the same address. // TODO: Make sure we don't try to connect more than once to the same address.
match addr.into() { self.io.push_back(Io::Connect(id, addr));
Address::Ipv4 { ip, port } => {
self.io
.push_back(Io::Connect(net::SocketAddr::new(net::IpAddr::V4(ip), port)));
}
Address::Ipv6 { ip, port } => {
self.io
.push_back(Io::Connect(net::SocketAddr::new(net::IpAddr::V6(ip), port)));
}
other => {
log::error!("Unsupported address type `{}`", other);
}
}
} }
/// Disconnect a peer. /// Disconnect a peer.
pub fn disconnect(&mut self, addr: net::SocketAddr, reason: DisconnectReason) { pub fn disconnect(&mut self, id: NodeId, reason: DisconnectReason) {
self.io.push_back(Io::Disconnect(addr, reason)); self.io.push_back(Io::Disconnect(id, reason));
} }
pub fn write(&mut self, remote: net::SocketAddr, msg: Message) { pub fn write(&mut self, remote: NodeId, msg: Message) {
debug!("Write {:?} to {}", &msg, remote.ip()); debug!("Write {:?} to {}", &msg, remote);
self.io.push_back(Io::Write(remote, vec![msg])); self.io.push_back(Io::Write(remote, vec![msg]));
} }
pub fn write_all(&mut self, remote: net::SocketAddr, msgs: impl IntoIterator<Item = Message>) { pub fn write_all(&mut self, remote: NodeId, msgs: impl IntoIterator<Item = Message>) {
self.io self.io
.push_back(Io::Write(remote, msgs.into_iter().collect())); .push_back(Io::Write(remote, msgs.into_iter().collect()));
} }
@ -81,7 +68,7 @@ impl Reactor {
peers: impl IntoIterator<Item = &'a Session>, peers: impl IntoIterator<Item = &'a Session>,
) { ) {
for peer in peers { for peer in peers {
self.write(peer.addr, msg.clone().into()); self.write(peer.id, msg.clone().into());
} }
} }

View File

@ -1,6 +1,5 @@
use crate::service::message; use crate::service::message;
use crate::service::message::Message; use crate::service::message::Message;
use crate::service::net;
use crate::service::storage; use crate::service::storage;
use crate::service::{Link, LocalTime, NodeId, Reactor, Rng}; use crate::service::{Link, LocalTime, NodeId, Reactor, Rng};
@ -18,10 +17,8 @@ pub enum PingState {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum State { pub enum State {
/// Initial peer state. For outgoing peers this /// Pre-handshake state.
/// means we've attempted a connection. For incoming /// TODO(cloudhead): Remove once noise handshake is implemented.
/// peers, this means they've successfully connected
/// to us.
#[default] #[default]
Initial, Initial,
/// State after successful handshake. /// State after successful handshake.
@ -29,8 +26,6 @@ pub enum State {
/// The peer's unique identifier. /// The peer's unique identifier.
id: NodeId, id: NodeId,
since: LocalTime, since: LocalTime,
/// Addresses this peer is reachable on.
addrs: Vec<message::Address>,
ping: PingState, ping: PingState,
}, },
/// When a peer is disconnected. /// When a peer is disconnected.
@ -43,8 +38,8 @@ pub enum Error {
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}`")] #[error("session not found for node `{0}`")]
NotFound(net::SocketAddr), NotFound(NodeId),
#[error("verification failed on fetch: {0}")] #[error("verification failed on fetch: {0}")]
VerificationFailed(#[from] storage::VerifyError), VerificationFailed(#[from] storage::VerifyError),
#[error("peer misbehaved")] #[error("peer misbehaved")]
@ -58,8 +53,8 @@ pub enum Error {
/// A peer session. Each connected peer will have one session. /// A peer session. Each connected peer will have one session.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Session { pub struct Session {
/// Peer address. /// Peer id.
pub addr: net::SocketAddr, pub id: NodeId,
/// Connection direction. /// Connection direction.
pub link: Link, pub link: Link,
/// Whether we should attempt to re-connect /// Whether we should attempt to re-connect
@ -82,10 +77,10 @@ pub struct Session {
} }
impl Session { impl Session {
pub fn new(addr: net::SocketAddr, link: Link, persistent: bool, rng: Rng) -> Self { pub fn new(id: NodeId, link: Link, persistent: bool, rng: Rng) -> Self {
Self { Self {
addr, id,
state: State::default(), state: State::Initial,
link, link,
subscribe: None, subscribe: None,
persistent, persistent,
@ -95,10 +90,6 @@ impl Session {
} }
} }
pub fn ip(&self) -> net::IpAddr {
self.addr.ip()
}
pub fn is_negotiated(&self) -> bool { pub fn is_negotiated(&self) -> bool {
matches!(self.state, State::Negotiated { .. }) matches!(self.state, State::Negotiated { .. })
} }
@ -120,7 +111,7 @@ impl Session {
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);
reactor.write(self.addr, Message::Ping(msg)); reactor.write(self.id, Message::Ping(msg));
} }
Ok(()) Ok(())
} }

View File

@ -1,6 +1,7 @@
use std::net; use std::net;
use bloomy::BloomFilter; use bloomy::BloomFilter;
use cyphernet::addr::{HostAddr, NetAddr};
use qcheck::Arbitrary; use qcheck::Arbitrary;
use crate::crypto; use crate::crypto;
@ -10,7 +11,7 @@ use crate::service::message::{
Address, Announcement, InventoryAnnouncement, Message, NodeAnnouncement, Ping, Address, Announcement, InventoryAnnouncement, Message, NodeAnnouncement, Ping,
RefsAnnouncement, Subscribe, ZeroBytes, RefsAnnouncement, Subscribe, ZeroBytes,
}; };
use crate::wire::message::MessageType; use crate::wire::MessageType;
pub use radicle::test::arbitrary::*; pub use radicle::test::arbitrary::*;
@ -104,19 +105,16 @@ impl Arbitrary for Message {
impl Arbitrary for Address { impl Arbitrary for Address {
fn arbitrary(g: &mut qcheck::Gen) -> Self { fn arbitrary(g: &mut qcheck::Gen) -> Self {
if bool::arbitrary(g) { let ip = if bool::arbitrary(g) {
Address::Ipv4 { net::IpAddr::V4(net::Ipv4Addr::from(u32::arbitrary(g)))
ip: net::Ipv4Addr::from(u32::arbitrary(g)),
port: u16::arbitrary(g),
}
} else { } else {
let octets: [u8; 16] = Arbitrary::arbitrary(g); let octets: [u8; 16] = Arbitrary::arbitrary(g);
net::IpAddr::V6(net::Ipv6Addr::from(octets))
Address::Ipv6 { };
ip: net::Ipv6Addr::from(octets), Address::from(NetAddr {
port: u16::arbitrary(g), host: HostAddr::Ip(ip),
} port: Some(u16::arbitrary(g)),
} })
} }
} }

View File

@ -21,10 +21,6 @@ impl radicle::node::Handle for Handle {
type Session = service::Session; type Session = service::Session;
type FetchLookup = FetchLookup; type FetchLookup = FetchLookup;
fn listening(&self) -> Result<std::net::SocketAddr, Error> {
unimplemented!()
}
fn fetch(&mut self, _id: Id) -> Result<FetchLookup, Error> { fn fetch(&mut self, _id: Id) -> Result<FetchLookup, Error> {
Ok(FetchLookup::NotFound) Ok(FetchLookup::NotFound)
} }

View File

@ -31,6 +31,7 @@ pub type Service<S, G> = service::Service<routing::Table, address::Book, S, G>;
pub struct Peer<S, G> { pub struct Peer<S, G> {
pub name: &'static str, pub name: &'static str,
pub service: Service<S, G>, pub service: Service<S, G>,
pub id: NodeId,
pub ip: net::IpAddr, pub ip: net::IpAddr,
pub rng: fastrand::Rng, pub rng: fastrand::Rng,
pub local_addr: net::SocketAddr, pub local_addr: net::SocketAddr,
@ -47,8 +48,12 @@ where
self.initialize() self.initialize()
} }
fn addr(&self) -> net::SocketAddr { fn addr(&self) -> Address {
net::SocketAddr::new(self.ip, DEFAULT_PORT) self.address()
}
fn id(&self) -> NodeId {
self.id
} }
} }
@ -108,6 +113,7 @@ where
) -> Self { ) -> Self {
let routing = routing::Table::memory().unwrap(); let routing = routing::Table::memory().unwrap();
let tracking = tracking::Config::memory().unwrap(); let tracking = tracking::Config::memory().unwrap();
let id = *config.signer.public_key();
let service = Service::new( let service = Service::new(
config.config, config.config,
config.local_time, config.local_time,
@ -124,6 +130,7 @@ where
Self { Self {
name, name,
service, service,
id,
ip, ip,
local_addr, local_addr,
rng: config.rng, rng: config.rng,
@ -141,7 +148,7 @@ where
} }
pub fn address(&self) -> Address { pub fn address(&self) -> Address {
simulator::Peer::addr(self).into() Address::from(net::SocketAddr::from((self.ip, 8776)))
} }
pub fn import_addresses<P>(&mut self, peers: P) pub fn import_addresses<P>(&mut self, peers: P)
@ -180,7 +187,7 @@ where
self.service.node_id() self.service.node_id()
} }
pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) { pub fn receive(&mut self, peer: NodeId, msg: Message) {
self.service.received_message(peer, msg); self.service.received_message(peer, msg);
} }
@ -225,20 +232,15 @@ where
} }
pub fn connect_from(&mut self, peer: &Self) { pub fn connect_from(&mut self, peer: &Self) {
let remote = simulator::Peer::<S, G>::addr(peer); let remote_id = simulator::Peer::<S, G>::id(peer);
let local = net::SocketAddr::new(self.ip, self.rng.u16(..));
self.initialize(); self.initialize();
self.service.connecting(remote, &local, Link::Inbound); self.service.connected(remote_id, Link::Inbound);
self.service.connected(remote, Link::Inbound); self.receive(remote_id, Message::init());
self.receive(
&remote,
Message::init(peer.node_id(), Some(Address::from(remote)).into()),
);
let mut msgs = self.messages(&remote); let mut msgs = self.messages(remote_id);
msgs.find(|m| matches!(m, Message::Initialize { .. })) msgs.find(|m| matches!(m, Message::Initialize { .. }))
.expect("`initialize` is sent"); .expect("`initialize` must be sent");
msgs.find(|m| { msgs.find(|m| {
matches!( matches!(
m, m,
@ -248,21 +250,20 @@ where
}) })
) )
}) })
.expect("`inventory-announcement` is sent"); .expect("`inventory-announcement` must be sent");
} }
pub fn connect_to(&mut self, peer: &Self) { pub fn connect_to(&mut self, peer: &Self) {
let remote = simulator::Peer::<S, G>::addr(peer); let remote_id = simulator::Peer::<S, G>::id(peer);
let remote_addr = simulator::Peer::<S, G>::addr(peer);
self.initialize(); self.initialize();
self.service.attempted(&remote); self.service.attempted(remote_id, &remote_addr);
self.service self.service.connected(remote_id, Link::Outbound);
.connecting(remote, &self.local_addr, Link::Outbound);
self.service.connected(remote, Link::Outbound);
let mut msgs = self.messages(&remote); let mut msgs = self.messages(remote_id);
msgs.find(|m| matches!(m, Message::Initialize { .. })) msgs.find(|m| matches!(m, Message::Initialize { .. }))
.expect("`initialize` is sent"); .expect("`initialize` must be sent");
msgs.find(|m| { msgs.find(|m| {
matches!( matches!(
m, m,
@ -272,19 +273,9 @@ where
}) })
) )
}) })
.expect("`inventory-announcement` is sent"); .expect("`inventory-announcement` must be sent");
self.receive( self.receive(remote_id, Message::init());
&remote,
Message::init(
peer.node_id(),
peer.config()
.listen
.clone()
.try_into()
.expect("within bound limits"),
),
);
} }
pub fn elapse(&mut self, duration: LocalDuration) { pub fn elapse(&mut self, duration: LocalDuration) {
@ -293,11 +284,11 @@ where
} }
/// Drain outgoing messages sent from this peer to the remote address. /// Drain outgoing messages sent from this peer to the remote address.
pub fn messages(&mut self, remote: &net::SocketAddr) -> impl Iterator<Item = Message> { pub fn messages(&mut self, remote: NodeId) -> impl Iterator<Item = Message> {
let mut msgs = Vec::new(); let mut msgs = Vec::new();
self.service.reactor().outbox().retain(|o| match o { self.service.reactor().outbox().retain(|o| match o {
Io::Write(a, messages) if a == remote => { Io::Write(a, messages) if *a == remote => {
msgs.extend(messages.clone()); msgs.extend(messages.clone());
false false
} }

View File

@ -6,15 +6,16 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, Range}; use std::ops::{Deref, DerefMut, Range};
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, io, net}; use std::{fmt, io};
use log::*; 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::Signer;
use crate::prelude::Address;
use crate::service::reactor::Io; use crate::service::reactor::Io;
use crate::service::{DisconnectReason, Event, Message}; use crate::service::{DisconnectReason, Event, Message, NodeId};
use crate::storage::WriteStorage; use crate::storage::WriteStorage;
use crate::test::peer::Service; use crate::test::peer::Service;
@ -23,10 +24,6 @@ pub const MIN_LATENCY: LocalDuration = LocalDuration::from_millis(1);
/// Maximum number of events buffered per peer. /// Maximum number of events buffered per peer.
pub const MAX_EVENTS: usize = 2048; pub const MAX_EVENTS: usize = 2048;
/// Identifier for a simulated node/peer.
/// The simulator requires each peer to have a distinct IP address.
type NodeId = std::net::IpAddr;
/// A simulated peer. Service instances have to be wrapped in this type to be simulated. /// A simulated peer. Service instances have to be wrapped in this type to be simulated.
pub trait Peer<S, G>: pub trait Peer<S, G>:
Deref<Target = Service<S, G>> + DerefMut<Target = Service<S, G>> + 'static Deref<Target = Service<S, G>> + DerefMut<Target = Service<S, G>> + 'static
@ -35,7 +32,9 @@ pub trait Peer<S, G>:
/// current time. /// current time.
fn init(&mut self); fn init(&mut self);
/// Get the peer address. /// Get the peer address.
fn addr(&self) -> net::SocketAddr; fn addr(&self) -> Address;
/// Get the peer id.
fn id(&self) -> NodeId;
} }
/// Simulated service input. /// Simulated service input.
@ -43,25 +42,22 @@ pub trait Peer<S, G>:
pub enum Input { pub enum Input {
/// Connection attempt underway. /// Connection attempt underway.
Connecting { Connecting {
/// Remote peer address. /// Remote peer id.
addr: net::SocketAddr, id: NodeId,
/// Address used to connect.
addr: Address,
}, },
/// New connection with a peer. /// New connection with a peer.
Connected { Connected {
/// Remote peer id. /// Remote peer id.
addr: net::SocketAddr, id: NodeId,
/// Local peer id.
local_addr: net::SocketAddr,
/// Link direction. /// Link direction.
link: Link, link: Link,
}, },
/// Disconnected from peer. /// Disconnected from peer.
Disconnected( Disconnected(NodeId, Rc<nakamoto::DisconnectReason<DisconnectReason>>),
net::SocketAddr,
Rc<nakamoto::DisconnectReason<DisconnectReason>>,
),
/// Received a message from a remote peer. /// Received a message from a remote peer.
Received(net::SocketAddr, Vec<Message>), Received(NodeId, Vec<Message>),
/// Used to advance the state machine after some wall time has passed. /// Used to advance the state machine after some wall time has passed.
Wake, Wake,
} }
@ -73,7 +69,7 @@ pub struct Scheduled {
pub node: NodeId, pub node: NodeId,
/// The remote peer from which this input originates. /// The remote peer from which this input originates.
/// If the input originates from the local node, this should be set to the zero address. /// If the input originates from the local node, this should be set to the zero address.
pub remote: net::SocketAddr, pub remote: NodeId,
/// The input being scheduled. /// The input being scheduled.
pub input: Input, pub input: Input,
} }
@ -88,19 +84,17 @@ impl fmt::Display for Scheduled {
Ok(()) Ok(())
} }
Input::Connected { Input::Connected {
addr, id: addr,
local_addr,
link: Link::Inbound, link: Link::Inbound,
.. ..
} => write!(f, "{} <== {}: Connected", local_addr, addr), } => write!(f, "{} <== {}: Connected", self.node, addr),
Input::Connected { Input::Connected {
local_addr, id: addr,
addr,
link: Link::Outbound, link: Link::Outbound,
.. ..
} => write!(f, "{} ==> {}: Connected", local_addr, addr), } => write!(f, "{} ==> {}: Connected", self.node, addr),
Input::Connecting { addr } => { Input::Connecting { id, .. } => {
write!(f, "{} => {}: Connecting", self.node, addr) write!(f, "{} => {}: Connecting", self.node, id)
} }
Input::Disconnected(addr, reason) => { Input::Disconnected(addr, reason) => {
write!(f, "{} =/= {}: Disconnected: {}", self.node, addr, reason) write!(f, "{} =/= {}: Disconnected: {}", self.node, addr, reason)
@ -139,7 +133,7 @@ impl Inbox {
} }
/// Get the last message sent between two peers. Only checks one direction. /// Get the last message sent between two peers. Only checks one direction.
fn last(&self, node: &NodeId, remote: &net::SocketAddr) -> Option<(&LocalTime, &Scheduled)> { fn last(&self, node: &NodeId, remote: &NodeId) -> Option<(&LocalTime, &Scheduled)> {
self.messages self.messages
.iter() .iter()
.rev() .rev()
@ -179,7 +173,7 @@ pub struct Simulation<S, G> {
/// Network partitions between two nodes. /// Network partitions between two nodes.
partitions: BTreeSet<(NodeId, NodeId)>, partitions: BTreeSet<(NodeId, NodeId)>,
/// Set of existing connections between nodes. /// Set of existing connections between nodes.
connections: BTreeMap<(NodeId, NodeId), u16>, connections: BTreeSet<(NodeId, NodeId)>,
/// Set of connection attempts. /// Set of connection attempts.
attempts: BTreeSet<(NodeId, NodeId)>, attempts: BTreeSet<(NodeId, NodeId)>,
/// Simulation options. /// Simulation options.
@ -207,7 +201,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
priority: VecDeque::new(), priority: VecDeque::new(),
partitions: BTreeSet::new(), partitions: BTreeSet::new(),
latencies: BTreeMap::new(), latencies: BTreeMap::new(),
connections: BTreeMap::new(), connections: BTreeSet::new(),
attempts: BTreeSet::new(), attempts: BTreeSet::new(),
opts, opts,
start_time: time, start_time: time,
@ -287,7 +281,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
) where ) where
P: Peer<S, G>, P: Peer<S, G>,
{ {
let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect(); let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.id(), p)).collect();
while self.step_(&mut nodes) { while self.step_(&mut nodes) {
if !pred(self) { if !pred(self) {
@ -300,7 +294,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
/// This function should be called until it returns `false`, or some desired state is reached. /// This function should be called until it returns `false`, or some desired state is reached.
/// Returns `true` if there are more messages to process. /// Returns `true` if there are more messages to process.
pub fn step<'a, P: Peer<S, G>>(&mut self, peers: impl IntoIterator<Item = &'a mut P>) -> bool { pub fn step<'a, P: Peer<S, G>>(&mut self, peers: impl IntoIterator<Item = &'a mut P>) -> bool {
let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect(); let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.id(), p)).collect();
self.step_(&mut nodes) self.step_(&mut nodes)
} }
@ -340,10 +334,10 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
// Schedule any messages in the pipes. // Schedule any messages in the pipes.
for peer in nodes.values_mut() { for peer in nodes.values_mut() {
let ip = peer.addr().ip(); let id = peer.id();
for o in peer.by_ref() { for o in peer.by_ref() {
self.schedule(&ip, o); self.schedule(&id, o);
} }
} }
// Next high-priority message. // Next high-priority message.
@ -370,42 +364,38 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
p.tick(time); p.tick(time);
match input { match input {
Input::Connecting { addr } => { Input::Connecting { id, addr } => {
if self.attempts.insert((node, addr.ip())) { if self.attempts.insert((node, id)) {
p.attempted(&addr); // TODO: Also call `inbound` for inbound attempts.
p.attempted(id, &addr);
} }
} }
Input::Connected { Input::Connected { id, link } => {
addr, let conn = (node, id);
local_addr,
link,
} => {
let conn = (node, addr.ip());
let attempted = link.is_outbound() && self.attempts.remove(&conn); let attempted = link.is_outbound() && self.attempts.remove(&conn);
if attempted || link.is_inbound() { if attempted || link.is_inbound() {
if self.connections.insert(conn, local_addr.port()).is_none() { if self.connections.insert(conn) {
p.connecting(addr, &local_addr, link); p.connected(id, link);
p.connected(addr, link);
} }
} }
} }
Input::Disconnected(addr, reason) => { Input::Disconnected(id, reason) => {
let conn = (node, addr.ip()); let conn = (node, id);
let attempt = self.attempts.remove(&conn); let attempt = self.attempts.remove(&conn);
let connection = self.connections.remove(&conn).is_some(); let connection = self.connections.remove(&conn);
// Can't be both attempting and connected. // Can't be both attempting and connected.
assert!(!(attempt && connection)); assert!(!(attempt && connection));
if attempt || connection { if attempt || connection {
p.disconnected(&addr, &reason); p.disconnected(id, &reason);
} }
} }
Input::Wake => p.wake(), Input::Wake => p.wake(),
Input::Received(addr, msgs) => { Input::Received(id, msgs) => {
for msg in msgs { for msg in msgs {
p.received_message(&addr, msg); p.received_message(id, msg);
} }
} }
} }
@ -433,14 +423,12 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
} }
// If the other end has disconnected the sender with some latency, there may not be // If the other end has disconnected the sender with some latency, there may not be
// a connection remaining to use. // a connection remaining to use.
let port = if let Some(port) = self.connections.get(&(node, receiver.ip())) { if self.connections.get(&(node, receiver)).is_none() {
*port
} else {
return; return;
}; }
let sender = node;
let sender: net::SocketAddr = (node, port).into(); if self.is_partitioned(sender, receiver) {
if self.is_partitioned(sender.ip(), receiver.ip()) {
// Drop message if nodes are partitioned. // Drop message if nodes are partitioned.
info!( info!(
target: "sim", target: "sim",
@ -452,10 +440,10 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
// Schedule message in the future, ensuring messages don't arrive out-of-order // Schedule message in the future, ensuring messages don't arrive out-of-order
// between two peers. // between two peers.
let latency = self.latency(node, receiver.ip()); let latency = self.latency(node, receiver);
let time = self let time = self
.inbox .inbox
.last(&receiver.ip(), &sender) .last(&receiver, &sender)
.map(|(k, _)| *k) .map(|(k, _)| *k)
.unwrap_or_else(|| self.time); .unwrap_or_else(|| self.time);
let time = time + latency; let time = time + latency;
@ -473,30 +461,28 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
time, time,
Scheduled { Scheduled {
remote: sender, remote: sender,
node: receiver.ip(), node: receiver,
input: Input::Received(sender, msgs), input: Input::Received(sender, msgs),
}, },
); );
} }
Io::Connect(remote) => { Io::Connect(remote, addr) => {
assert!(remote.ip() != node, "self-connections are not allowed"); assert!(remote != node, "self-connections are not allowed");
// Create an ephemeral sockaddr for the connecting (local) node. let latency = self.latency(node, remote);
let local_addr: net::SocketAddr = net::SocketAddr::new(node, self.rng.u16(8192..));
let latency = self.latency(node, remote.ip());
self.inbox.insert( self.inbox.insert(
self.time + MIN_LATENCY, self.time + MIN_LATENCY,
Scheduled { Scheduled {
node, node,
remote, remote,
input: Input::Connecting { addr: remote }, input: Input::Connecting { id: remote, addr },
}, },
); );
// Fail to connect if the nodes are partitioned. // Fail to connect if the nodes are partitioned.
if self.is_partitioned(node, remote.ip()) { if self.is_partitioned(node, remote) {
log::info!(target: "sim", "{} -/-> {} (partitioned)", node, remote.ip()); log::info!(target: "sim", "{} -/-> {} (partitioned)", node, remote);
// Sometimes, the service gets a failure input, other times it just hangs. // Sometimes, the service gets a failure input, other times it just hangs.
if self.rng.bool() { if self.rng.bool() {
@ -521,11 +507,10 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
// The remote will get the connection attempt with some latency. // The remote will get the connection attempt with some latency.
self.time + latency, self.time + latency,
Scheduled { Scheduled {
node: remote.ip(), node: remote,
remote: local_addr, remote: node,
input: Input::Connected { input: Input::Connected {
addr: local_addr, id: node,
local_addr: remote,
link: Link::Inbound, link: Link::Inbound,
}, },
}, },
@ -537,8 +522,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
remote, remote,
node, node,
input: Input::Connected { input: Input::Connected {
addr: remote, id: remote,
local_addr,
link: Link::Outbound, link: Link::Outbound,
}, },
}, },
@ -558,21 +542,20 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
// //
// It's also possible that the connection was only attempted and never succeeded, // It's also possible that the connection was only attempted and never succeeded,
// in which case we would return here. // in which case we would return here.
let Some(port) = self.connections.get(&(node, remote.ip())) else { if !self.connections.contains(&(node, remote)) {
debug!(target: "sim", "Ignoring disconnect of {remote} from {node}"); debug!(target: "sim", "Ignoring disconnect of {remote} from {node}");
return; return;
}; };
let local_addr: net::SocketAddr = (node, *port).into(); let latency = self.latency(node, remote);
let latency = self.latency(node, remote.ip());
// The remote node receives the disconnection with some delay. // The remote node receives the disconnection with some delay.
self.inbox.insert( self.inbox.insert(
self.time + latency, self.time + latency,
Scheduled { Scheduled {
node: remote.ip(), node: remote,
remote: local_addr, remote: node,
input: Input::Disconnected( input: Input::Disconnected(
local_addr, node,
Rc::new(nakamoto::DisconnectReason::ConnectionError( Rc::new(nakamoto::DisconnectReason::ConnectionError(
io::Error::from(io::ErrorKind::ConnectionReset).into(), io::Error::from(io::ErrorKind::ConnectionReset).into(),
)), )),
@ -595,7 +578,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
Scheduled { Scheduled {
node, node,
// The remote is not applicable for this type of output. // The remote is not applicable for this type of output.
remote: ([0, 0, 0, 0], 0).into(), remote: [0; 32].into(),
input: Input::Wake, input: Input::Wake,
}, },
); );

View File

@ -64,28 +64,28 @@ fn test_ping_response() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::Ping(Ping { Message::Ping(Ping {
ponglen: Ping::MAX_PONG_ZEROES, ponglen: Ping::MAX_PONG_ZEROES,
zeroes: ZeroBytes::new(42), zeroes: ZeroBytes::new(42),
}), }),
); );
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(bob.id()).next(),
Some(Message::Pong { zeroes }) if zeroes.len() == Ping::MAX_PONG_ZEROES as usize, Some(Message::Pong { zeroes }) if zeroes.len() == Ping::MAX_PONG_ZEROES as usize,
"respond with correctly formatted pong", "respond with correctly formatted pong",
); );
alice.connect_to(&eve); alice.connect_to(&eve);
alice.receive( alice.receive(
&eve.addr(), eve.id(),
Message::Ping(Ping { Message::Ping(Ping {
ponglen: Ping::MAX_PONG_ZEROES + 1, ponglen: Ping::MAX_PONG_ZEROES + 1,
zeroes: ZeroBytes::new(42), zeroes: ZeroBytes::new(42),
}), }),
); );
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
None, None,
"ignore unsupported ping message", "ignore unsupported ping message",
); );
@ -101,7 +101,7 @@ fn test_disconnecting_unresponsive_peer() {
alice.elapse(STALE_CONNECTION_TIMEOUT + LocalDuration::from_secs(1)); alice.elapse(STALE_CONNECTION_TIMEOUT + LocalDuration::from_secs(1));
alice alice
.outbox() .outbox()
.find(|m| matches!(m, &Io::Disconnect(addr, _) if addr == bob.addr())) .find(|m| matches!(m, &Io::Disconnect(addr, _) if addr == bob.id()))
.expect("disconnect an unresponsive bob"); .expect("disconnect an unresponsive bob");
} }
@ -117,7 +117,7 @@ fn test_connection_kept_alive() {
) )
.initialize([&mut alice, &mut bob]); .initialize([&mut alice, &mut bob]);
alice.command(service::Command::Connect(bob.addr())); alice.command(service::Command::Connect(bob.id(), bob.address()));
sim.run_while([&mut alice, &mut bob], |s| !s.is_settled()); sim.run_while([&mut alice, &mut bob], |s| !s.is_settled());
assert_eq!(1, alice.sessions().negotiated().count(), "bob connects"); assert_eq!(1, alice.sessions().negotiated().count(), "bob connects");
@ -148,11 +148,11 @@ fn test_outbound_connection() {
.service .service
.sessions() .sessions()
.negotiated() .negotiated()
.map(|(ip, _, _)| *ip) .map(|(id, _)| *id)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert!(peers.contains(&eve.addr())); assert!(peers.contains(&eve.id()));
assert!(peers.contains(&bob.addr())); assert!(peers.contains(&bob.id()));
} }
#[test] #[test]
@ -168,11 +168,11 @@ fn test_inbound_connection() {
.service .service
.sessions() .sessions()
.negotiated() .negotiated()
.map(|(ip, _, _)| *ip) .map(|(id, _)| *id)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert!(peers.contains(&eve.addr())); assert!(peers.contains(&eve.id()));
assert!(peers.contains(&bob.addr())); assert!(peers.contains(&bob.id()));
} }
#[test] #[test]
@ -185,7 +185,7 @@ fn test_persistent_peer_connect() {
MockStorage::empty(), MockStorage::empty(),
peer::Config { peer::Config {
config: Config { config: Config {
connect: vec![bob.address(), eve.address()], connect: vec![(bob.id(), bob.address()), (eve.id(), eve.address())],
..Config::default() ..Config::default()
}, },
..peer::Config::default() ..peer::Config::default()
@ -195,23 +195,11 @@ fn test_persistent_peer_connect() {
alice.initialize(); alice.initialize();
let mut outbox = alice.outbox(); let mut outbox = alice.outbox();
assert_matches!(outbox.next(), Some(Io::Connect(a)) if a == bob.addr()); assert_matches!(outbox.next(), Some(Io::Connect(a, _)) if a == bob.id());
assert_matches!(outbox.next(), Some(Io::Connect(a)) if a == eve.addr()); assert_matches!(outbox.next(), Some(Io::Connect(a, _)) if a == eve.id());
assert_matches!(outbox.next(), None); assert_matches!(outbox.next(), None);
} }
#[test]
#[ignore]
fn test_wrong_peer_version() {
// TODO
}
#[test]
#[ignore]
fn test_wrong_peer_magic() {
// TODO
}
#[test] #[test]
fn test_inventory_sync() { fn test_inventory_sync() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
@ -229,7 +217,7 @@ fn test_inventory_sync() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: projs.clone().try_into().unwrap(), inventory: projs.clone().try_into().unwrap(),
@ -325,7 +313,7 @@ fn test_inventory_pruning() {
alice.connect_to(&bob); alice.connect_to(&bob);
for num_projs in test.peer_projects { for num_projs in test.peer_projects {
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: test::arbitrary::vec::<Id>(num_projs).try_into().unwrap(), inventory: test::arbitrary::vec::<Id>(num_projs).try_into().unwrap(),
@ -380,7 +368,7 @@ fn test_inventory_relay_bad_timestamp() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: BoundedVec::new(), inventory: BoundedVec::new(),
@ -392,7 +380,7 @@ fn test_inventory_relay_bad_timestamp() {
assert_matches!( assert_matches!(
alice.outbox().next(), alice.outbox().next(),
Some(Io::Disconnect(addr, DisconnectReason::Error(session::Error::InvalidTimestamp(t)))) Some(Io::Disconnect(addr, DisconnectReason::Error(session::Error::InvalidTimestamp(t))))
if addr == bob.addr() && t == timestamp if addr == bob.id() && t == timestamp
); );
} }
@ -406,12 +394,12 @@ fn test_announcement_rebroadcast() {
let received = test::gossip::messages(6, alice.local_time(), MAX_TIME_DELTA); let received = test::gossip::messages(6, alice.local_time(), MAX_TIME_DELTA);
for msg in received.iter().cloned() { for msg in received.iter().cloned() {
alice.receive(&bob.addr(), msg); alice.receive(bob.id(), msg);
} }
alice.connect_from(&eve); alice.connect_from(&eve);
alice.receive( alice.receive(
&eve.addr(), eve.id(),
Message::Subscribe(Subscribe { Message::Subscribe(Subscribe {
filter: Filter::default(), filter: Filter::default(),
since: Timestamp::MIN, since: Timestamp::MIN,
@ -419,7 +407,7 @@ fn test_announcement_rebroadcast() {
}), }),
); );
let relayed = alice.messages(&eve.addr()).collect::<Vec<_>>(); let relayed = alice.messages(eve.id()).collect::<Vec<_>>();
assert_eq!(relayed, received); assert_eq!(relayed, received);
} }
@ -443,13 +431,13 @@ fn test_announcement_rebroadcast_timestamp_filtered() {
.chain(third.iter()) .chain(third.iter())
.cloned() .cloned()
{ {
alice.receive(&bob.addr(), msg); alice.receive(bob.id(), msg);
} }
// Eve subscribes to messages within the period of the second batch only. // Eve subscribes to messages within the period of the second batch only.
alice.connect_from(&eve); alice.connect_from(&eve);
alice.receive( alice.receive(
&eve.addr(), eve.id(),
Message::Subscribe(Subscribe { Message::Subscribe(Subscribe {
filter: Filter::default(), filter: Filter::default(),
since: alice.local_time().as_secs(), since: alice.local_time().as_secs(),
@ -457,7 +445,7 @@ fn test_announcement_rebroadcast_timestamp_filtered() {
}), }),
); );
let relayed = alice.messages(&eve.addr()).collect::<Vec<_>>(); let relayed = alice.messages(eve.id()).collect::<Vec<_>>();
assert_eq!(relayed.len(), second.len()); assert_eq!(relayed.len(), second.len());
assert_eq!(relayed, second); assert_eq!(relayed, second);
} }
@ -470,56 +458,56 @@ fn test_announcement_relay() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_to(&eve); alice.connect_to(&eve);
alice.receive(&bob.addr(), bob.inventory_announcement()); alice.receive(bob.id(), bob.inventory_announcement());
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)) Some(Message::Announcement(_))
); );
alice.receive(&bob.addr(), bob.inventory_announcement()); alice.receive(bob.id(), bob.inventory_announcement());
assert!( assert!(
alice.messages(&eve.addr()).next().is_none(), alice.messages(eve.id()).next().is_none(),
"Another inventory with the same timestamp is ignored" "Another inventory with the same timestamp is ignored"
); );
bob.elapse(LocalDuration::from_mins(1)); bob.elapse(LocalDuration::from_mins(1));
alice.receive(&bob.addr(), bob.inventory_announcement()); alice.receive(bob.id(), bob.inventory_announcement());
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"Another inventory with a fresher timestamp is relayed" "Another inventory with a fresher timestamp is relayed"
); );
alice.receive(&bob.addr(), bob.node_announcement()); alice.receive(bob.id(), bob.node_announcement());
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"A node announcement with the same timestamp as the inventory is relayed" "A node announcement with the same timestamp as the inventory is relayed"
); );
alice.receive(&bob.addr(), bob.node_announcement()); alice.receive(bob.id(), bob.node_announcement());
assert!(alice.messages(&eve.addr()).next().is_none(), "Only once"); assert!(alice.messages(eve.id()).next().is_none(), "Only once");
alice.receive(&eve.addr(), eve.node_announcement()); alice.receive(eve.id(), eve.node_announcement());
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(bob.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"A node announcement from Eve is relayed to Bob" "A node announcement from Eve is relayed to Bob"
); );
assert!( assert!(
alice.messages(&eve.addr()).next().is_none(), alice.messages(eve.id()).next().is_none(),
"But not back to Eve" "But not back to Eve"
); );
eve.elapse(LocalDuration::from_mins(1)); eve.elapse(LocalDuration::from_mins(1));
alice.receive(&bob.addr(), eve.node_announcement()); alice.receive(bob.id(), eve.node_announcement());
assert!( assert!(
alice.messages(&bob.addr()).next().is_none(), alice.messages(bob.id()).next().is_none(),
"Bob already know about this message, since he sent it" "Bob already know about this message, since he sent it"
); );
assert!( assert!(
alice.messages(&eve.addr()).next().is_none(), alice.messages(eve.id()).next().is_none(),
"Eve already know about this message, since she signed it" "Eve already know about this message, since she signed it"
); );
} }
@ -563,31 +551,31 @@ fn test_refs_announcement_relay() {
alice.track_repo(&bob_inv[2], tracking::Scope::All).unwrap(); alice.track_repo(&bob_inv[2], tracking::Scope::All).unwrap();
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_to(&eve); alice.connect_to(&eve);
alice.receive(&eve.addr(), Message::Subscribe(Subscribe::all())); alice.receive(eve.id(), Message::Subscribe(Subscribe::all()));
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[0])); alice.receive(bob.id(), bob.refs_announcement(bob_inv[0]));
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"A refs announcement from Bob is relayed to Eve" "A refs announcement from Bob is relayed to Eve"
); );
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[0])); alice.receive(bob.id(), bob.refs_announcement(bob_inv[0]));
assert!( assert!(
alice.messages(&eve.addr()).next().is_none(), alice.messages(eve.id()).next().is_none(),
"The same ref announement is not relayed" "The same ref announement is not relayed"
); );
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[1])); alice.receive(bob.id(), bob.refs_announcement(bob_inv[1]));
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"But a different one is" "But a different one is"
); );
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[2])); alice.receive(bob.id(), bob.refs_announcement(bob_inv[2]));
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(_)), Some(Message::Announcement(_)),
"And a third one is as well" "And a third one is as well"
); );
@ -603,9 +591,9 @@ fn test_refs_announcement_no_subscribe() {
alice.track_repo(&id, tracking::Scope::All).unwrap(); alice.track_repo(&id, tracking::Scope::All).unwrap();
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_to(&eve); alice.connect_to(&eve);
alice.receive(&bob.addr(), bob.refs_announcement(id)); alice.receive(bob.id(), bob.refs_announcement(id));
assert!(alice.messages(&eve.addr()).next().is_none()); assert!(alice.messages(eve.id()).next().is_none());
} }
#[test] #[test]
@ -621,7 +609,7 @@ fn test_inventory_relay() {
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_from(&eve); alice.connect_from(&eve);
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: inv.clone(), inventory: inv.clone(),
@ -631,7 +619,7 @@ fn test_inventory_relay() {
), ),
); );
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(Announcement { Some(Message::Announcement(Announcement {
node, node,
message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }), message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }),
@ -640,13 +628,13 @@ fn test_inventory_relay() {
if node == bob.node_id() && timestamp == now if node == bob.node_id() && timestamp == now
); );
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(bob.id()).next(),
None, None,
"The inventory is not sent back to Bob" "The inventory is not sent back to Bob"
); );
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: inv.clone(), inventory: inv.clone(),
@ -656,13 +644,13 @@ fn test_inventory_relay() {
), ),
); );
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
None, None,
"Sending the same inventory again doesn't trigger a relay" "Sending the same inventory again doesn't trigger a relay"
); );
alice.receive( alice.receive(
&bob.addr(), bob.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: inv.clone(), inventory: inv.clone(),
@ -672,7 +660,7 @@ fn test_inventory_relay() {
), ),
); );
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(eve.id()).next(),
Some(Message::Announcement(Announcement { Some(Message::Announcement(Announcement {
node, node,
message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }), message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }),
@ -684,7 +672,7 @@ fn test_inventory_relay() {
// Inventory from Eve relayed to Bob. // Inventory from Eve relayed to Bob.
alice.receive( alice.receive(
&eve.addr(), eve.id(),
Message::inventory( Message::inventory(
InventoryAnnouncement { InventoryAnnouncement {
inventory: inv, inventory: inv,
@ -694,7 +682,7 @@ fn test_inventory_relay() {
), ),
); );
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(bob.id()).next(),
Some(Message::Announcement(Announcement { Some(Message::Announcement(Announcement {
node, node,
message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }), message: AnnouncementMessage::Inventory(InventoryAnnouncement { timestamp, .. }),
@ -714,7 +702,7 @@ fn test_persistent_peer_reconnect() {
MockStorage::empty(), MockStorage::empty(),
peer::Config { peer::Config {
config: Config { config: Config {
connect: vec![bob.address(), eve.address()], connect: vec![(bob.id(), bob.address()), (eve.id(), eve.address())],
..Config::default() ..Config::default()
}, },
..peer::Config::default() ..peer::Config::default()
@ -733,10 +721,10 @@ fn test_persistent_peer_reconnect() {
let ips = alice let ips = alice
.sessions() .sessions()
.negotiated() .negotiated()
.map(|(ip, _, _)| *ip) .map(|(id, _)| *id)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert!(ips.contains(&bob.addr())); assert!(ips.contains(&bob.id()));
assert!(ips.contains(&eve.addr())); assert!(ips.contains(&eve.id()));
// ... Negotiated ... // ... Negotiated ...
// //
@ -748,25 +736,25 @@ fn test_persistent_peer_reconnect() {
// A non-transient disconnect, such as one requested by the user will not trigger // A non-transient disconnect, such as one requested by the user will not trigger
// a reconnection. // a reconnection.
alice.disconnected( alice.disconnected(
&eve.addr(), eve.id(),
&nakamoto::DisconnectReason::DialError(error.clone()), &nakamoto::DisconnectReason::DialError(error.clone()),
); );
assert_matches!(alice.outbox().next(), None); assert_matches!(alice.outbox().next(), None);
for _ in 0..MAX_CONNECTION_ATTEMPTS { for _ in 0..MAX_CONNECTION_ATTEMPTS {
alice.disconnected( alice.disconnected(
&bob.addr(), bob.id(),
&nakamoto::DisconnectReason::ConnectionError(error.clone()), &nakamoto::DisconnectReason::ConnectionError(error.clone()),
); );
assert_matches!(alice.outbox().next(), Some(Io::Connect(a)) if a == bob.addr()); assert_matches!(alice.outbox().next(), Some(Io::Connect(a, _)) if a == bob.id());
assert_matches!(alice.outbox().next(), None); assert_matches!(alice.outbox().next(), None);
alice.attempted(&bob.addr()); alice.attempted(bob.id(), &bob.address());
} }
// After the max connection attempts, a disconnect doesn't trigger a reconnect. // After the max connection attempts, a disconnect doesn't trigger a reconnect.
alice.disconnected( alice.disconnected(
&bob.addr(), bob.id(),
&nakamoto::DisconnectReason::ConnectionError(error), &nakamoto::DisconnectReason::ConnectionError(error),
); );
assert_matches!(alice.outbox().next(), None); assert_matches!(alice.outbox().next(), None);
@ -803,19 +791,19 @@ fn test_maintain_connections() {
let error = Arc::new(io::Error::from(io::ErrorKind::ConnectionReset)); let error = Arc::new(io::Error::from(io::ErrorKind::ConnectionReset));
for peer in connected.iter() { for peer in connected.iter() {
alice.disconnected( alice.disconnected(
&peer.addr(), peer.id(),
&nakamoto::DisconnectReason::ConnectionError(error.clone()), &nakamoto::DisconnectReason::ConnectionError(error.clone()),
); );
let addr = alice let id = alice
.outbox() .outbox()
.find_map(|o| match o { .find_map(|o| match o {
Io::Connect(addr) => Some(addr), Io::Connect(id, _) => Some(id),
_ => None, _ => None,
}) })
.expect("Alice connects to a new peer"); .expect("Alice connects to a new peer");
assert!(addr != peer.addr()); assert!(id != peer.id());
unconnected.retain(|p| p.addr() != addr); unconnected.retain(|p| p.id() != id);
} }
assert!( assert!(
unconnected.is_empty(), unconnected.is_empty(),
@ -848,8 +836,8 @@ fn test_push_and_pull() {
local::register(alice.storage().clone()); local::register(alice.storage().clone());
// Alice and Bob connect to Eve. // Alice and Bob connect to Eve.
alice.command(service::Command::Connect(eve.addr())); alice.command(service::Command::Connect(eve.id(), eve.address()));
bob.command(service::Command::Connect(eve.addr())); bob.command(service::Command::Connect(eve.id(), eve.address()));
// Alice creates a new project. // Alice creates a new project.
let (proj_id, _, _) = rad::init( let (proj_id, _, _) = rad::init(
@ -902,7 +890,7 @@ fn test_push_and_pull() {
.unwrap() .unwrap()
.is_some()); .is_some());
assert_matches!( assert_matches!(
sim.events(&bob.ip).next(), sim.events(&bob.id).next(),
Some(service::Event::RefsFetched { from, .. }) Some(service::Event::RefsFetched { from, .. })
if from == eve.node_id(), if from == eve.node_id(),
"Bob fetched from Eve" "Bob fetched from Eve"
@ -947,10 +935,10 @@ fn prop_inventory_exchange_dense() {
} }
// Fully-connected. // Fully-connected.
bob.command(Command::Connect(alice.addr())); bob.command(Command::Connect(alice.id(), alice.address()));
bob.command(Command::Connect(eve.addr())); bob.command(Command::Connect(eve.id(), eve.address()));
eve.command(Command::Connect(alice.addr())); eve.command(Command::Connect(alice.id(), alice.address()));
eve.command(Command::Connect(bob.addr())); eve.command(Command::Connect(bob.id(), bob.address()));
let mut peers: HashMap<_, _> = [ let mut peers: HashMap<_, _> = [
(alice.node_id(), alice), (alice.node_id(), alice),

View File

@ -1,33 +1,27 @@
pub mod message; mod message;
pub mod transcode; mod transport;
use std::collections::{BTreeMap, HashMap, VecDeque}; pub use message::{AddressType, MessageType};
pub use transport::Transport;
use std::collections::BTreeMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::net;
use std::ops::Deref; use std::ops::Deref;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{io, mem}; use std::{io, mem};
use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
use nakamoto_net as nakamoto;
use nakamoto_net::{Link, LocalTime};
use crate::address;
use crate::crypto::hash::Digest; use crate::crypto::hash::Digest;
use crate::crypto::{PublicKey, Signature, Signer, Unverified}; use crate::crypto::{PublicKey, Signature, Unverified};
use crate::deserializer::Deserializer;
use crate::git; use crate::git;
use crate::git::fmt; use crate::git::fmt;
use crate::identity::Id; use crate::identity::Id;
use crate::node; use crate::node;
use crate::prelude::*; use crate::prelude::*;
use crate::service; use crate::service::filter;
use crate::service::reactor::Io;
use crate::service::{filter, routing, session};
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::wire::transcode::{Framer, Handshake, HandshakeResult, MuxMsg, Transcode};
/// The default type we use to represent sizes on the wire. /// The default type we use to represent sizes on the wire.
/// ///
@ -474,194 +468,6 @@ impl Decode for node::Features {
} }
} }
#[derive(Debug)]
pub struct Inbox<T: Transcode> {
pub pipeline: Framer<T>,
pub deserializer: Deserializer,
}
#[derive(Debug)]
pub struct Wire<R, S, W, G, H: Handshake> {
handshakes: HashMap<net::SocketAddr, H>,
inner_queue: VecDeque<nakamoto::Io<service::Event, service::DisconnectReason>>,
inboxes: HashMap<net::SocketAddr, Inbox<H::Transcoder>>,
inner: service::Service<R, S, W, G>,
}
impl<R, S, W, G, H: Handshake> Wire<R, S, W, G, H> {
pub fn new(inner: service::Service<R, S, W, G>) -> Self {
Self {
handshakes: HashMap::new(),
inner_queue: Default::default(),
inboxes: HashMap::new(),
inner,
}
}
}
impl<R, S, W, G, H> nakamoto::Protocol for Wire<R, S, W, G, H>
where
R: routing::Store,
S: address::Store,
W: WriteStorage + 'static,
G: Signer,
H: Handshake,
{
type Event = service::Event;
type Command = service::Command;
type DisconnectReason = service::DisconnectReason;
fn initialize(&mut self, time: LocalTime) {
self.inner.initialize(time)
}
fn tick(&mut self, now: nakamoto::LocalTime) {
self.inner.tick(now)
}
fn wake(&mut self) {
self.inner.wake()
}
fn command(&mut self, cmd: Self::Command) {
self.inner.command(cmd)
}
fn attempted(&mut self, addr: &std::net::SocketAddr) {
self.inner.attempted(addr)
}
fn connected(&mut self, addr: net::SocketAddr, local_addr: &net::SocketAddr, link: Link) {
self.handshakes.insert(addr, H::new(link));
self.inner.connecting(addr, local_addr, link)
}
fn disconnected(
&mut self,
addr: &net::SocketAddr,
reason: nakamoto::DisconnectReason<service::DisconnectReason>,
) {
self.handshakes.remove(addr);
self.inboxes.remove(addr);
self.inner.disconnected(addr, &reason)
}
fn received_bytes(&mut self, addr: &net::SocketAddr, raw_bytes: &[u8]) {
if let Some(handshake) = self.handshakes.remove(addr) {
debug_assert!(!self.inboxes.contains_key(addr));
match handshake.step(raw_bytes) {
HandshakeResult::Next(handshake, reply) => {
self.handshakes.insert(*addr, handshake);
if !reply.is_empty() {
self.inner_queue
.push_back(nakamoto::Io::Write(*addr, reply));
}
return;
}
HandshakeResult::Complete(transcoder, reply, link) => {
log::debug!("handshake with peer {} is complete", addr);
if !reply.is_empty() {
self.inner_queue
.push_back(nakamoto::Io::Write(*addr, reply));
}
let pipeline = Framer::new(transcoder);
self.inboxes.insert(
*addr,
Inbox {
pipeline,
deserializer: Deserializer::new(256),
},
);
self.inner.connected(*addr, link);
}
HandshakeResult::Error(err) => {
log::error!("invalid handshake input. Details: {}", err);
self.inner_queue.push_back(nakamoto::Io::Disconnect(
*addr,
service::DisconnectReason::Error(session::Error::Handshake(
err.to_string(),
)),
));
return;
}
}
}
if let Some(Inbox {
pipeline,
deserializer,
}) = self.inboxes.get_mut(addr)
{
pipeline.input(raw_bytes);
for frame in pipeline {
let Ok(msg) = MuxMsg::try_from(frame) else {
// TODO: Disconnect peer.
log::error!("Message frame with invalid channel structure from {}", addr);
return;
};
match msg.channel {
0 => deserializer.input(&msg.data),
1 => { /* TODO: Send to git worker */ }
wrong_channel => {
// TODO: Disconnect peer.
log::error!("Wrong message channel {} from peer {}", wrong_channel, addr);
return;
}
};
}
for message in deserializer {
match message {
Ok(msg) => self.inner.received_message(addr, msg),
Err(err) => {
// TODO: Disconnect peer.
log::error!("Invalid message received from {}: {}", addr, err);
return;
}
}
}
} else {
log::debug!("Received message from unknown peer {}", addr);
}
}
}
impl<R, S, W, G, H: Handshake> Iterator for Wire<R, S, W, G, H> {
type Item = nakamoto::Io<service::Event, service::DisconnectReason>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(event) = self.inner_queue.pop_front() {
return Some(event);
}
match self.inner.next() {
Some(Io::Write(addr, msgs)) => {
let mut buf = Vec::new();
for msg in msgs {
log::debug!("Write {:?} to {}", &msg, addr.ip());
msg.encode(&mut buf)
.expect("writing to an in-memory buffer doesn't fail");
}
let Inbox { pipeline, .. } = self.inboxes.get_mut(&addr).expect(
"broken handshake implementation: data sent before handshake was complete",
);
let data = pipeline.frame(buf).expect("oversized data for a frame");
let msg = MuxMsg { channel: 0, data };
Some(nakamoto::Io::Write(addr, msg.into()))
}
Some(Io::Event(e)) => Some(nakamoto::Io::Event(e)),
Some(Io::Connect(a)) => Some(nakamoto::Io::Connect(a)),
Some(Io::Disconnect(a, r)) => Some(nakamoto::Io::Disconnect(a, r)),
Some(Io::Wakeup(d)) => Some(nakamoto::Io::Wakeup(d)),
None => None,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,11 +1,12 @@
use std::{io, mem, net}; use std::{io, mem, net};
use byteorder::{NetworkEndian, ReadBytesExt}; use byteorder::{NetworkEndian, ReadBytesExt};
use cyphernet::addr::{Addr, HostAddr, NetAddr};
use crate::prelude::*; use crate::prelude::*;
use crate::service;
use crate::service::message::*; use crate::service::message::*;
use crate::wire; use crate::wire;
use crate::wire::{Decode, Encode};
/// Message type. /// Message type.
#[repr(u16)] #[repr(u16)]
@ -64,6 +65,22 @@ impl Message {
} }
} }
impl netservices::Frame for Message {
type Error = wire::Error;
fn unmarshall(mut reader: impl io::Read) -> Result<Option<Self>, Self::Error> {
match Message::decode(&mut reader) {
Ok(msg) => Ok(Some(msg)),
Err(wire::Error::Io(_)) => Ok(None),
Err(err) => Err(err),
}
}
fn marshall(&self, mut writer: impl io::Write) -> Result<usize, Self::Error> {
self.encode(&mut writer).map_err(wire::Error::from)
}
}
/// Address type. /// Address type.
#[repr(u8)] #[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -82,11 +99,12 @@ impl From<AddressType> for u8 {
impl From<&Address> for AddressType { impl From<&Address> for AddressType {
fn from(a: &Address) -> Self { fn from(a: &Address) -> Self {
match a { match a.host {
Address::Ipv4 { .. } => AddressType::Ipv4, HostAddr::Ip(net::IpAddr::V4(_)) => AddressType::Ipv4,
Address::Ipv6 { .. } => AddressType::Ipv6, HostAddr::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
Address::Hostname { .. } => AddressType::Hostname, HostAddr::Dns(_) => AddressType::Hostname,
Address::Onion { .. } => AddressType::Onion, HostAddr::Tor(_) => AddressType::Onion,
_ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
} }
} }
} }
@ -169,11 +187,7 @@ impl wire::Encode for Message {
let mut n = self.type_id().encode(writer)?; let mut n = self.type_id().encode(writer)?;
match self { match self {
Self::Initialize { id, version, addrs } => { Self::Initialize {} => {}
n += id.encode(writer)?;
n += version.encode(writer)?;
n += addrs.encode(writer)?;
}
Self::Subscribe(Subscribe { Self::Subscribe(Subscribe {
filter, filter,
since, since,
@ -216,13 +230,7 @@ impl wire::Decode for Message {
let type_id = reader.read_u16::<NetworkEndian>()?; let type_id = reader.read_u16::<NetworkEndian>()?;
match MessageType::try_from(type_id) { match MessageType::try_from(type_id) {
Ok(MessageType::Initialize) => { Ok(MessageType::Initialize) => Ok(Self::Initialize {}),
let id = NodeId::decode(reader)?;
let version = u32::decode(reader)?;
let addrs = BoundedVec::<Address, { service::ADDRESS_LIMIT }>::decode(reader)?;
Ok(Self::Initialize { id, version, addrs })
}
Ok(MessageType::Subscribe) => { Ok(MessageType::Subscribe) => {
let filter = Filter::decode(reader)?; let filter = Filter::decode(reader)?;
let since = Timestamp::decode(reader)?; let since = Timestamp::decode(reader)?;
@ -288,20 +296,21 @@ impl wire::Encode for Address {
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> { fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
let mut n = 0; let mut n = 0;
match self { match self.host {
Self::Ipv4 { ip, port } => { HostAddr::Ip(net::IpAddr::V4(ip)) => {
n += u8::from(AddressType::Ipv4).encode(writer)?; n += u8::from(AddressType::Ipv4).encode(writer)?;
n += ip.octets().encode(writer)?; n += ip.octets().encode(writer)?;
n += port.encode(writer)?;
} }
Self::Ipv6 { ip, port } => { HostAddr::Ip(net::IpAddr::V6(ip)) => {
n += u8::from(AddressType::Ipv6).encode(writer)?; n += u8::from(AddressType::Ipv6).encode(writer)?;
n += ip.octets().encode(writer)?; n += ip.octets().encode(writer)?;
n += port.encode(writer)?;
} }
Self::Hostname { .. } => todo!(), _ => {
Self::Onion { .. } => todo!(), todo!();
}
} }
n += self.port().encode(writer)?;
Ok(n) Ok(n)
} }
} }
@ -309,21 +318,18 @@ impl wire::Encode for Address {
impl wire::Decode for Address { impl wire::Decode for Address {
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> { fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
let addrtype = reader.read_u8()?; let addrtype = reader.read_u8()?;
let host = match AddressType::try_from(addrtype) {
match AddressType::try_from(addrtype) {
Ok(AddressType::Ipv4) => { Ok(AddressType::Ipv4) => {
let octets: [u8; 4] = wire::Decode::decode(reader)?; let octets: [u8; 4] = wire::Decode::decode(reader)?;
let ip = net::Ipv4Addr::from(octets); let ip = net::Ipv4Addr::from(octets);
let port = u16::decode(reader)?;
Ok(Self::Ipv4 { ip, port }) HostAddr::Ip(net::IpAddr::V4(ip))
} }
Ok(AddressType::Ipv6) => { Ok(AddressType::Ipv6) => {
let octets: [u8; 16] = wire::Decode::decode(reader)?; let octets: [u8; 16] = wire::Decode::decode(reader)?;
let ip = net::Ipv6Addr::from(octets); let ip = net::Ipv6Addr::from(octets);
let port = u16::decode(reader)?;
Ok(Self::Ipv6 { ip, port }) HostAddr::Ip(net::IpAddr::V6(ip))
} }
Ok(AddressType::Hostname) => { Ok(AddressType::Hostname) => {
todo!(); todo!();
@ -331,8 +337,14 @@ impl wire::Decode for Address {
Ok(AddressType::Onion) => { Ok(AddressType::Onion) => {
todo!(); todo!();
} }
Err(other) => Err(wire::Error::UnknownAddressType(other)), Err(other) => return Err(wire::Error::UnknownAddressType(other)),
} };
let port = u16::decode(reader)?;
Ok(Self::from(NetAddr {
host,
port: Some(port),
}))
} }
} }

View File

@ -1,223 +0,0 @@
use std::collections::VecDeque;
use std::convert::Infallible;
use std::io;
use std::io::Read;
use nakamoto_net::Link;
// TODO: Implement Try trait once stabilized
/// Result of a state-machine transition.
pub enum HandshakeResult<H: Handshake, T: Transcode> {
/// Handshake is not completed; we proceed to the next handshake stage.
Next(H, Vec<u8>),
/// Handshake is completed; we now can communicate in a secure way.
Complete(T, Vec<u8>, Link),
/// Handshake has failed with some error.
Error(H::Error),
}
/// State machine implementation of a handshake protocol which can be run by
/// peers.
pub trait Handshake: Sized {
/// The resulting transcoder which will be constructed upon a successful
/// handshake
type Transcoder: Transcode;
/// Errors which may happen during the handshake.
type Error: std::error::Error;
/// Create a new handshake state-machine.
fn new(link: Link) -> Self;
/// Advance the state-machine to the next state.
fn step(self, input: &[u8]) -> HandshakeResult<Self, Self::Transcoder>;
/// Returns direction of the handshake protocol.
fn link(&self) -> Link;
}
/// Dumb handshake structure which runs void protocol.
#[derive(Debug)]
pub struct NoHandshake(Link);
impl Handshake for NoHandshake {
type Transcoder = PlainTranscoder;
type Error = Infallible;
fn new(link: Link) -> Self {
NoHandshake(link)
}
fn step(self, _input: &[u8]) -> HandshakeResult<Self, Self::Transcoder> {
HandshakeResult::Complete(PlainTranscoder, vec![], self.0)
}
fn link(&self) -> Link {
self.0
}
}
/// Trait allowing transcoding a stream using some form of stream encryption and/or encoding.
pub trait Transcode {
/// Decodes data received from the remote peer and update the internal state
/// of the transcoder, if necessary.
fn decode(&mut self, data: &[u8]) -> Vec<u8>;
/// Encodes data before sending it to the remote peer.
fn encode(&mut self, data: Vec<u8>) -> Vec<u8>;
}
/// Transcoder which does nothing.
#[derive(Debug, Default)]
pub struct PlainTranscoder;
impl Transcode for PlainTranscoder {
fn decode(&mut self, data: &[u8]) -> Vec<u8> {
data.to_vec()
}
fn encode(&mut self, data: Vec<u8>) -> Vec<u8> {
data
}
}
pub type Frame = Vec<u8>;
#[derive(Copy, Clone, Debug)]
pub struct OversizedData(usize);
#[derive(Debug, Default)]
pub struct Framer<T: Transcode> {
input: VecDeque<u8>,
inner: T,
}
impl<T: Transcode> Framer<T> {
pub fn new(inner: T) -> Self {
Framer {
input: Default::default(),
inner,
}
}
pub fn input(&mut self, encoded: &[u8]) {
self.input.extend(self.inner.decode(encoded));
}
pub fn frame(&mut self, decoded: Vec<u8>) -> Result<Frame, OversizedData> {
let mut data = self.inner.encode(decoded);
let len = data.len();
let len = u8::try_from(len).map_err(|_| OversizedData(len))?;
let len = len.to_be_bytes();
let mut buf = Vec::with_capacity(data.len() + 2);
buf.extend(len);
buf.append(&mut data);
Ok(buf)
}
}
impl<T: Transcode> Iterator for Framer<T> {
type Item = Frame;
fn next(&mut self) -> Option<Self::Item> {
if self.input.len() < 2 {
return None;
}
let mut len = [0u8; 2];
self.input
.read_exact(&mut len)
.expect("the length is checked");
let len = u16::from_be_bytes(len) as usize;
if self.input.len() < 2 + len {
return None;
}
self.input.pop_front();
self.input.pop_front();
let reminder = self.input.split_off(len);
let mut data = vec![0u8; len];
self.input.read_exact(&mut data).expect("checked length");
self.input = reminder;
Some(data)
}
}
#[derive(Copy, Clone, Debug)]
pub struct ChannelError;
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct MuxMsg {
pub channel: u16,
pub data: Vec<u8>,
}
impl From<MuxMsg> for Frame {
fn from(mut msg: MuxMsg) -> Self {
let channel = msg.channel.to_be_bytes();
let mut data = Vec::with_capacity(msg.data.len() + 2);
data.extend(channel);
data.append(&mut msg.data);
data
}
}
impl TryFrom<Frame> for MuxMsg {
type Error = ChannelError;
fn try_from(frame: Frame) -> Result<Self, Self::Error> {
if frame.len() < 2 {
return Err(ChannelError);
}
let mut channel = [0u8; 2];
let mut cursor = io::Cursor::new(frame);
cursor
.read_exact(&mut channel)
.expect("the length is checked");
let channel = u16::from_be_bytes(channel);
Ok(MuxMsg {
channel,
data: cursor.into_inner(),
})
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::deserializer::Deserializer;
#[test]
fn decode() {
let mut pipeline = Framer::new(PlainTranscoder);
let mut deser = Deserializer::<String>::new(512);
let data = [
0x00, 0x04, 0x00, 0x00, b'a', b'b', 0x00, 0x07, 0x00, 0x01, b'M', b'a', b'x', b'i',
b'm',
];
let mut expected_payloads = [(0u16, b"ab".to_vec()), (1, b"Maxim".to_vec())].into_iter();
let mut expected_msgs = ["ab", "Maxim"].into_iter();
for byte in data {
// Writing data byte by byte, ensuring that the reading is not broken
pipeline.input(&[byte]);
for frame in &mut pipeline {
let msg = MuxMsg::try_from(frame).unwrap();
let (channel, data) = expected_payloads.next().unwrap();
deser.input(&data);
assert_eq!(msg, MuxMsg { channel, data });
}
}
for msg in deser {
let msg = msg.unwrap();
assert_eq!(msg, expected_msgs.next().unwrap());
}
}
}

View File

@ -0,0 +1,384 @@
//! Implementation of the transport protocol.
//!
//! We use the Noise XK handshake pattern to establish an encrypted stream with a remote peer.
//! The handshake itself is implemented in the external [`netservices`] crate.
use std::collections::VecDeque;
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd;
use std::sync::Arc;
use std::time::Duration;
use std::{io, net};
use cyphernet::addr::{Addr as _, HostAddr, PeerAddr};
use nakamoto_net::{DisconnectReason, Link, LocalTime};
use netservices::noise::NoiseXk;
use netservices::wire::{ListenerEvent, NetAccept, NetTransport, SessionEvent};
use netservices::NetSession;
use radicle::collections::HashMap;
use radicle::crypto::Negotiator;
use radicle::node::NodeId;
use radicle::storage::WriteStorage;
use crate::crypto::Signer;
use crate::service::reactor::Io;
use crate::service::{routing, session, Message, Service};
use crate::{address, service};
/// The peer session type.
pub type Session<N> = NoiseXk<N>;
/// Reactor action.
type Action<G> = reactor::Action<NetAccept<Session<G>>, NetTransport<Session<G>, Message>>;
/// Peer connection state machine.
#[derive(Debug)]
enum Peer {
/// The initial state before handshake is completed.
Connecting { link: Link },
/// The state after handshake is completed.
/// Peers in this state are handled by the underlying service.
Connected { link: Link, id: NodeId },
/// The state after a peer was disconnected, either during handshake,
/// or once connected.
Disconnected {
id: NodeId,
reason: DisconnectReason<service::DisconnectReason>,
},
}
impl Peer {
/// Return a new connecting peer.
fn connecting(link: Link) -> Self {
Self::Connecting { link }
}
/// Switch to connected state.
fn connected(&mut self, id: NodeId) {
if let Self::Connecting { link } = self {
*self = Self::Connected { link: *link, id };
} else {
panic!("Peer::connected: session for {} is already established", id);
}
}
/// Switch to disconnected state.
fn disconnected(&mut self, reason: DisconnectReason<service::DisconnectReason>) {
if let Self::Connected { id, .. } = self {
*self = Self::Disconnected { id: *id, reason };
} else {
panic!("Peer::disconnected: session is not connected");
}
}
}
/// Transport protocol implementation for a set of peers.
pub struct Transport<R, S, W, G: Negotiator> {
/// Backing service instance.
service: Service<R, S, W, G>,
/// Used to performs X25519 key exchange.
keypair: G,
/// Internal queue of actions to send to the reactor.
actions: VecDeque<Action<G>>,
/// Peer sessions.
peers: HashMap<RawFd, Peer>,
/// SOCKS5 proxy address.
proxy: net::SocketAddr,
}
impl<R, S, W, G> Transport<R, S, W, G>
where
R: routing::Store,
S: address::Store,
W: WriteStorage + 'static,
G: Signer + Negotiator,
{
pub fn new(
mut service: Service<R, S, W, G>,
keypair: G,
proxy: net::SocketAddr,
clock: LocalTime,
) -> Self {
service.initialize(clock);
Self {
service,
keypair,
proxy,
actions: VecDeque::new(),
peers: HashMap::default(),
}
}
fn by_id(&self, id: &NodeId) -> RawFd {
self.connected()
.find(|(_, i)| *i == id)
.map(|(fd, _)| fd)
.unwrap()
}
fn connected(&self) -> impl Iterator<Item = (RawFd, &NodeId)> {
self.peers.iter().filter_map(|(fd, peer)| {
if let Peer::Connected { id, .. } = peer {
Some((*fd, id))
} else {
None
}
})
}
fn disconnect(&mut self, fd: RawFd, reason: DisconnectReason<service::DisconnectReason>) {
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", "Disconnecting peer with fd {} ({})..", fd, reason);
peer.disconnected(reason);
self.actions.push_back(Action::UnregisterTransport(fd));
}
}
impl<R, S, W, G> reactor::Handler for Transport<R, S, W, G>
where
R: routing::Store + Send,
S: address::Store + Send,
W: WriteStorage + Send + 'static,
G: Signer + Negotiator + Send,
{
type Listener = NetAccept<Session<G>>;
type Transport = NetTransport<Session<G>, Message>;
type Command = service::Command;
fn handle_wakeup(&mut self) {
self.service.wake()
}
fn handle_listener_event(
&mut self,
socket_addr: net::SocketAddr,
event: ListenerEvent<Session<G>>,
duration: Duration,
) {
self.service.tick(LocalTime::from_secs(duration.as_secs()));
match event {
ListenerEvent::Accepted(session) => {
log::debug!(
target: "transport",
"Accepted inbound peer connection from {}..",
session.transition_addr()
);
self.peers
.insert(session.as_raw_fd(), Peer::connecting(Link::Inbound));
let transport = match NetTransport::<Session<G>, Message>::upgrade(session) {
Ok(transport) => transport,
Err(err) => {
log::error!(target: "transport", "Failed to upgrade accepted peer socket: {err}");
return;
}
};
self.service.accepted(socket_addr);
self.actions
.push_back(reactor::Action::RegisterTransport(transport))
}
ListenerEvent::Error(err) => {
log::error!(target: "transport", "Error listening for inbound connections: {err}");
}
}
}
fn handle_transport_event(
&mut self,
fd: RawFd,
event: SessionEvent<Session<G>, Message>,
duration: Duration,
) {
self.service.tick(LocalTime::from_secs(duration.as_secs()));
match event {
SessionEvent::SessionEstablished(node_id) => {
log::debug!(target: "transport", "Session established with {node_id}");
let conflicting = self
.connected()
.filter(|(_, id)| **id == node_id)
.map(|(fd, _)| fd)
.collect::<Vec<_>>();
for fd in conflicting {
log::warn!(
target: "transport", "Closing conflicting session with {node_id} (fd={fd})"
);
self.disconnect(
fd,
DisconnectReason::DialError(
io::Error::from(io::ErrorKind::AlreadyExists).into(),
),
);
}
let Some(peer) = self.peers.get_mut(&fd) else {
log::error!(target: "transport", "Session not found for fd {fd}");
return;
};
let Peer::Connecting { link } = peer else {
log::error!(
target: "transport",
"Session for {node_id} was either not found, or in an invalid state"
);
return;
};
let link = *link;
peer.connected(node_id);
self.service.connected(node_id, link);
}
SessionEvent::Message(msg) => {
if let Some(Peer::Connected { link, id }) = self.peers.get(&fd) {
log::debug!(
target: "transport", "Received message {:?} from {} ({:?})", msg, id, link
);
self.service.received_message(*id, msg);
} else {
log::warn!(target: "transport", "Dropping message from unconnected peer with fd {fd}");
}
}
SessionEvent::FrameFailure(_err) => {
// TODO(cloudhead): Include error in reason.
self.disconnect(
fd,
DisconnectReason::Protocol(service::DisconnectReason::Error(
session::Error::Misbehavior,
)),
);
}
SessionEvent::ConnectionFailure(err) => {
self.disconnect(fd, DisconnectReason::ConnectionError(Arc::new(err)));
}
SessionEvent::Disconnected => {
self.disconnect(
fd,
DisconnectReason::Protocol(service::DisconnectReason::Peer),
);
}
}
}
fn handle_command(&mut self, cmd: Self::Command) {
self.service.command(cmd)
}
fn handle_error(&mut self, err: reactor::Error<net::SocketAddr, RawFd>) {
match err {
reactor::Error::ListenerUnknown(id) => {
log::error!(target: "transport", "Received error: unknown listener {}", id);
}
reactor::Error::PeerUnknown(id) => {
log::error!(target: "transport", "Received error: unknown peer {}", id);
}
reactor::Error::PeerDisconnected(fd, err) => {
log::error!(target: "transport", "Received error: peer {} disconnected: {}", fd, err);
self.actions.push_back(Action::UnregisterTransport(fd));
}
}
}
fn handover_listener(&mut self, _listener: Self::Listener) {
panic!("Transport::handover_listener: listener handover is not supported");
}
fn handover_transport(&mut self, transport: Self::Transport) {
let fd = transport.as_raw_fd();
if let Some(Peer::Disconnected { id, reason }) = self.peers.get(&fd) {
// Disconnect TCP stream.
drop(transport);
self.service.disconnected(*id, reason);
} else {
todo!("The handover protocol is not implemented");
}
}
}
impl<R, S, W, G> Iterator for Transport<R, S, W, G>
where
R: routing::Store,
S: address::Store,
W: WriteStorage + 'static,
G: Signer + Negotiator,
{
type Item = reactor::Action<NetAccept<Session<G>>, NetTransport<Session<G>, Message>>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(event) = self.actions.pop_front() {
return Some(event);
}
while let Some(ev) = self.service.next() {
match ev {
Io::Write(node_id, msgs) => {
log::debug!(
target: "transport", "Sending {} message(s) to {}", msgs.len(), node_id
);
let fd = self.by_id(&node_id);
return Some(reactor::Action::Send(fd, msgs));
}
Io::Event(_e) => {
log::warn!(
target: "transport", "Events are not currently supported"
);
}
Io::Connect(node_id, addr) => {
let socket_addr = match addr.host {
HostAddr::Ip(ip) => net::SocketAddr::new(ip, addr.port()),
HostAddr::Dns(_) => todo!(),
_ => self.proxy,
};
if self.connected().any(|(_, id)| id == &node_id) {
log::error!(
target: "transport",
"Attempt to connect to already connected peer {node_id}"
);
break;
}
match NetTransport::<Session<G>, Message>::connect(
PeerAddr::new(node_id, socket_addr),
&self.keypair,
) {
Ok(transport) => {
self.service.attempted(node_id, &socket_addr.into());
self.peers
.insert(transport.as_raw_fd(), Peer::connecting(Link::Outbound));
return Some(reactor::Action::RegisterTransport(transport));
}
Err(err) => {
self.service
.disconnected(node_id, &DisconnectReason::DialError(Arc::new(err)));
break;
}
}
}
Io::Disconnect(node_id, reason) => {
let fd = self.by_id(&node_id);
self.disconnect(fd, DisconnectReason::Protocol(reason));
return self.actions.pop_back();
}
Io::Wakeup(d) => return Some(reactor::Action::Wakeup(d.into())),
}
}
None
}
}

View File

@ -15,7 +15,7 @@ base64 = { version= "0.13" }
byteorder = { version = "1.4" } byteorder = { version = "1.4" }
crossbeam-channel = { version = "0.5.6" } crossbeam-channel = { version = "0.5.6" }
ed25519-compact = { version = "2.0.2", features = ["pem"] } ed25519-compact = { version = "2.0.2", features = ["pem"] }
cyphernet = { version = "0", optional = true } cyphernet = { version = "0" }
fastrand = { version = "1.8.0" } fastrand = { version = "1.8.0" }
git-ref-format = { version = "0", features = ["serde", "macro"] } git-ref-format = { version = "0", features = ["serde", "macro"] }
multibase = { version = "0.9.1" } multibase = { version = "0.9.1" }
@ -49,7 +49,7 @@ version = "0"
[dependencies.radicle-crypto] [dependencies.radicle-crypto]
path = "../radicle-crypto" path = "../radicle-crypto"
version = "0" version = "0"
features = ["git-ref-format", "ssh", "sqlite"] features = ["git-ref-format", "ssh", "sqlite", "cyphernet"]
[dependencies.radicle-ssh] [dependencies.radicle-ssh]
path = "../radicle-ssh" path = "../radicle-ssh"

View File

@ -1,9 +1,9 @@
mod features; mod features;
use std::io;
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::path::Path; use std::path::Path;
use std::{io, net};
use crate::crypto::PublicKey; use crate::crypto::PublicKey;
use crate::identity::Id; use crate::identity::Id;
@ -37,8 +37,6 @@ pub trait Handle {
/// The error returned by all methods. /// The error returned by all methods.
type Error: std::error::Error; type Error: std::error::Error;
/// Wait for the node's listening socket to be bound.
fn listening(&self) -> Result<net::SocketAddr, Self::Error>;
/// Retrieve or update the project from network. /// Retrieve or update the project from network.
fn fetch(&mut self, id: Id) -> Result<Self::FetchLookup, Self::Error>; fn fetch(&mut self, id: Id) -> Result<Self::FetchLookup, Self::Error>;
/// Start tracking the given project. Doesn't do anything if the project is already /// Start tracking the given project. Doesn't do anything if the project is already
@ -202,10 +200,6 @@ impl Handle for Node {
todo!(); todo!();
} }
fn listening(&self) -> Result<net::SocketAddr, Error> {
todo!();
}
fn inventory(&self) -> Result<chan::Receiver<Id>, Error> { fn inventory(&self) -> Result<chan::Receiver<Id>, Error> {
todo!(); todo!();
} }