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:
parent
909b99bb7c
commit
aa02c52a2f
|
|
@ -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"
|
||||||
|
|
|
||||||
13
Cargo.toml
13
Cargo.toml
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 Diffie–Hellman 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 Diffie–Hellman.
|
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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] {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue