parent
9b9b5ca996
commit
ac3eba09a1
|
|
@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
value are asked to keep the substring `/radicle:{YOUR_VERSION}/` which allows
|
||||
for better telemetry regarding version distribution on the network.
|
||||
To opt-out of sending any meaningful user agent, set `node.userAgent = null`.
|
||||
- In addition to connections via SOCKS proxy and Tor for `*.onion` names, now
|
||||
connections via SOCKS proxy and I2P for `*.i2p{,.alt}` names is now supported.
|
||||
To enable making connections via I2P, configure `node.i2p`.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ name = "rad"
|
|||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["tor"]
|
||||
default = ["i2p", "tor"]
|
||||
i2p = ["radicle/i2p"]
|
||||
tor = ["radicle/tor"]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -111,12 +111,13 @@ $ rad config schema
|
|||
"type": "string"
|
||||
},
|
||||
"ConnectAddress": {
|
||||
"description": "A node address to connect to. Format: An Ed25519 public key in multibase encoding, followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', followed by a TCP port number.",
|
||||
"description": "A node address to connect to. Format: An Ed25519 public key in multibase encoding, followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion name, or an I2P address, followed by the symbol ':', followed by a TCP port number.",
|
||||
"type": "string",
|
||||
"pattern": "^.+@.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$",
|
||||
"examples": [
|
||||
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@rosa.radicle.xyz:8776",
|
||||
"z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||
"z6Mkvky2mnSYCTUMKRdAUoZXBXLLKtnWEkWeYQcGjjnmobAU@f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
|
||||
"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi@seed.example.com:8776",
|
||||
"z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5@192.0.2.0:31337"
|
||||
]
|
||||
|
|
@ -260,6 +261,10 @@ $ rad config schema
|
|||
"description": "Onion address config.",
|
||||
"$ref": "#/$defs/AddressConfig"
|
||||
},
|
||||
"i2p": {
|
||||
"description": "I2P address config.",
|
||||
"$ref": "#/$defs/AddressConfig"
|
||||
},
|
||||
"network": {
|
||||
"description": "Peer-to-peer network.",
|
||||
"$ref": "#/$defs/Network",
|
||||
|
|
@ -383,10 +388,11 @@ $ rad config schema
|
|||
]
|
||||
},
|
||||
"Address": {
|
||||
"description": "An IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', followed by a TCP port number.",
|
||||
"description": "An IP address, or a DNS name, or a Tor onion name, or an I2P address,followed by the symbol ':', followed by a TCP port number.",
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||
"f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
|
||||
"seed.example.com:8776",
|
||||
"192.0.2.0:31337"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ build = "build.rs"
|
|||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["backtrace", "systemd", "structured-logger", "socket2", "tor"]
|
||||
default = ["backtrace", "i2p", "systemd", "structured-logger", "socket2", "tor"]
|
||||
i2p = ["cyphernet/i2p", "radicle/i2p", "radicle-protocol/i2p"]
|
||||
systemd = ["dep:radicle-systemd"]
|
||||
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radicle-protocol/test", "qcheck", "snapbox"]
|
||||
tor = ["cyphernet/tor", "radicle/tor", "radicle-protocol/tor"]
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use radicle::collections::{RandomMap, RandomSet};
|
|||
use radicle::crypto;
|
||||
use radicle::node::Link;
|
||||
use radicle::node::NodeId;
|
||||
#[cfg(feature = "tor")]
|
||||
#[cfg(any(feature = "i2p", feature = "tor"))]
|
||||
use radicle::node::config::AddressConfig;
|
||||
use radicle::storage::WriteStorage;
|
||||
use radicle_protocol::deserializer::Deserializer;
|
||||
|
|
@ -1083,6 +1083,30 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
|
|||
signer: G,
|
||||
config: &radicle::node::Config,
|
||||
) -> io::Result<WireSession<G>> {
|
||||
#[cfg(any(feature = "i2p", feature = "tor"))]
|
||||
fn proxy_or_forward<H: std::fmt::Display>(
|
||||
config: &AddressConfig,
|
||||
global_proxy: Option<net::SocketAddr>,
|
||||
host: H,
|
||||
port: u16,
|
||||
) -> io::Result<NetAddr<InetHost>> {
|
||||
match config {
|
||||
// In proxy mode, simply use the configured proxy address.
|
||||
// This takes precedence over any global proxy.
|
||||
AddressConfig::Proxy { address } => Ok((*address).into()),
|
||||
// In "forward" mode, if a global proxy is set, we use that, otherwise
|
||||
// we treat the address as a regular DNS name.
|
||||
AddressConfig::Forward => Ok(global_proxy
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| NetAddr::new(InetHost::Dns(host.to_string()), port))),
|
||||
// If address type support isn't configured, refuse to connect.
|
||||
AddressConfig::Drop => Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"no configuration found for address type",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Determine what address to establish a TCP connection with, given the remote peer
|
||||
// address and our node configuration.
|
||||
let inet_addr: NetAddr<InetHost> = match (&remote_addr.host, config.proxy) {
|
||||
|
|
@ -1093,27 +1117,11 @@ pub fn dial<G: Ecdh<Pk = NodeId>>(
|
|||
(HostName::Dns(dns), None) => NetAddr::new(InetHost::Dns(dns.clone()), remote_addr.port),
|
||||
// For onion addresses, handle with care.
|
||||
#[cfg(feature = "tor")]
|
||||
(HostName::Tor(onion), proxy) => match config.onion {
|
||||
// In onion proxy mode, simply use the configured proxy address.
|
||||
// This takes precedence over any global proxy.
|
||||
AddressConfig::Proxy { address } => address.into(),
|
||||
// In "forward" mode, if a global proxy is set, we use that, otherwise
|
||||
// we treat `.onion` addresses as regular DNS names.
|
||||
AddressConfig::Forward => {
|
||||
if let Some(proxy) = proxy {
|
||||
proxy.into()
|
||||
} else {
|
||||
NetAddr::new(InetHost::Dns(onion.to_string()), remote_addr.port)
|
||||
}
|
||||
}
|
||||
// If onion address support isn't configured, refuse to connect.
|
||||
AddressConfig::Drop => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"no configuration found for .onion addresses",
|
||||
));
|
||||
}
|
||||
},
|
||||
(HostName::Tor(onion), proxy) => {
|
||||
proxy_or_forward(&config.onion, proxy, onion, remote_addr.port)?
|
||||
}
|
||||
#[cfg(feature = "i2p")]
|
||||
(HostName::I2p(i2p), proxy) => proxy_or_forward(&config.i2p, proxy, i2p, remote_addr.port)?,
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ edition.workspace = true
|
|||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
i2p = ["cypheraddr/i2p", "radicle/i2p"]
|
||||
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "qcheck"]
|
||||
tor = ["cypheraddr/tor", "radicle/tor"]
|
||||
|
||||
|
|
@ -35,4 +36,4 @@ pastey = "0.2"
|
|||
qcheck = { workspace = true }
|
||||
qcheck-macros = { workspace = true }
|
||||
radicle = { workspace = true, features = ["test"] }
|
||||
radicle-crypto = { workspace = true, features = ["test", "cyphernet"] }
|
||||
radicle-crypto = { workspace = true, features = ["test", "cyphernet"] }
|
||||
|
|
@ -28,8 +28,6 @@ use radicle::node;
|
|||
use radicle::node::address;
|
||||
use radicle::node::address::Store as _;
|
||||
use radicle::node::address::{AddressBook, AddressType, KnownAddress};
|
||||
#[cfg(feature = "tor")]
|
||||
use radicle::node::config::AddressConfig;
|
||||
use radicle::node::config::{PeerConfig, RateLimit};
|
||||
use radicle::node::device::Device;
|
||||
use radicle::node::refs::Store as _;
|
||||
|
|
@ -2652,11 +2650,18 @@ where
|
|||
///
|
||||
/// If the [`Address`] is an `.onion` address and the service supports onion
|
||||
/// routing then this will return `true`.
|
||||
///
|
||||
/// # I2P
|
||||
///
|
||||
/// If the [`Address`] is an I2P address and the service supports I2P
|
||||
/// connections then this will return `true`.
|
||||
fn is_supported_address(&self, address: &Address) -> bool {
|
||||
match AddressType::from(address) {
|
||||
// Only consider onion addresses if configured.
|
||||
#[cfg(feature = "tor")]
|
||||
AddressType::Onion => self.config.onion != AddressConfig::Drop,
|
||||
AddressType::Onion => self.config.onion != radicle::node::config::AddressConfig::Drop,
|
||||
#[cfg(feature = "i2p")]
|
||||
AddressType::I2p => self.config.i2p != radicle::node::config::AddressConfig::Drop,
|
||||
AddressType::Dns | AddressType::Ipv4 | AddressType::Ipv6 => true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ use std::string::FromUtf8Error;
|
|||
|
||||
use bytes::{Buf, BufMut};
|
||||
|
||||
#[cfg(feature = "i2p")]
|
||||
use cypheraddr::i2p;
|
||||
#[cfg(feature = "tor")]
|
||||
use cypheraddr::tor;
|
||||
|
||||
|
|
@ -60,6 +62,9 @@ pub enum Invalid {
|
|||
#[cfg(feature = "tor")]
|
||||
#[error("invalid onion address: {0}")]
|
||||
OnionAddr(#[from] tor::OnionAddrDecodeError),
|
||||
#[cfg(feature = "i2p")]
|
||||
#[error("invalid i2p address: {0}")]
|
||||
I2pAddr(#[from] i2p::I2pAddrParseError),
|
||||
#[error("invalid timestamp: {actual_millis} millis")]
|
||||
Timestamp { actual_millis: u64 },
|
||||
|
||||
|
|
@ -266,6 +271,13 @@ impl Encode for cypheraddr::tor::OnionAddrV3 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "i2p")]
|
||||
impl Encode for i2p::I2pAddr {
|
||||
fn encode(&self, buf: &mut impl BufMut) {
|
||||
self.to_string().encode(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for UserAgent {
|
||||
fn encode(&self, buf: &mut impl BufMut) {
|
||||
self.as_ref().encode(buf)
|
||||
|
|
@ -531,6 +543,16 @@ impl Decode for tor::OnionAddrV3 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "i2p")]
|
||||
impl Decode for i2p::I2pAddr {
|
||||
fn decode(buf: &mut impl Buf) -> Result<Self, Error> {
|
||||
let s = String::decode(buf)?;
|
||||
let addr = i2p::I2pAddr::from_str(&s).map_err(Invalid::from)?;
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Timestamp {
|
||||
fn encode(&self, buf: &mut impl BufMut) {
|
||||
self.deref().encode(buf)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use std::{mem, net};
|
|||
|
||||
use bytes::Buf;
|
||||
use bytes::BufMut;
|
||||
#[cfg(feature = "i2p")]
|
||||
use cypheraddr::i2p;
|
||||
#[cfg(feature = "tor")]
|
||||
use cypheraddr::tor;
|
||||
use cypheraddr::{HostName, NetAddr};
|
||||
|
|
@ -83,6 +85,8 @@ pub enum AddressType {
|
|||
Dns = 3,
|
||||
#[cfg(feature = "tor")]
|
||||
Onion = 4,
|
||||
#[cfg(feature = "i2p")]
|
||||
I2p = 5,
|
||||
}
|
||||
|
||||
impl From<AddressType> for u8 {
|
||||
|
|
@ -99,6 +103,8 @@ impl From<&Address> for AddressType {
|
|||
HostName::Dns(_) => AddressType::Dns,
|
||||
#[cfg(feature = "tor")]
|
||||
HostName::Tor(_) => AddressType::Onion,
|
||||
#[cfg(feature = "i2p")]
|
||||
HostName::I2p(_) => AddressType::I2p,
|
||||
_ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +120,8 @@ impl TryFrom<u8> for AddressType {
|
|||
3 => Ok(AddressType::Dns),
|
||||
#[cfg(feature = "tor")]
|
||||
4 => Ok(AddressType::Onion),
|
||||
#[cfg(feature = "i2p")]
|
||||
5 => Ok(AddressType::I2p),
|
||||
_ => Err(other),
|
||||
}
|
||||
}
|
||||
|
|
@ -366,6 +374,11 @@ impl wire::Encode for Address {
|
|||
u8::from(AddressType::Onion).encode(buf);
|
||||
addr.encode(buf);
|
||||
}
|
||||
#[cfg(feature = "i2p")]
|
||||
HostName::I2p(ref addr) => {
|
||||
u8::from(AddressType::I2p).encode(buf);
|
||||
addr.encode(buf);
|
||||
}
|
||||
_ => {
|
||||
unimplemented!(
|
||||
"Encoding not defined for addresses of the same type as the following: {:?}",
|
||||
|
|
@ -405,6 +418,12 @@ impl wire::Decode for Address {
|
|||
|
||||
HostName::Tor(onion)
|
||||
}
|
||||
#[cfg(feature = "i2p")]
|
||||
Ok(AddressType::I2p) => {
|
||||
let i2p: i2p::I2pAddr = wire::Decode::decode(buf)?;
|
||||
|
||||
HostName::I2p(i2p)
|
||||
}
|
||||
Err(other) => return Err(wire::Invalid::AddressType { actual: other }.into()),
|
||||
};
|
||||
let port = u16::decode(buf)?;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ rust-version.workspace = true
|
|||
bstr = ["radicle-git-ref-format/bstr"]
|
||||
default = []
|
||||
gix = ["radicle-oid/gix"]
|
||||
i2p = ["cyphernet/i2p"]
|
||||
test = ["tempfile", "qcheck", "radicle-crypto/test", "radicle-cob/test"]
|
||||
logger = ["colored", "chrono"]
|
||||
qcheck = [
|
||||
|
|
|
|||
|
|
@ -468,11 +468,11 @@ impl TryFrom<&sqlite::Value> for Alias {
|
|||
feature = "schemars",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(description = "\
|
||||
An IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', \
|
||||
followed by a TCP port number.",
|
||||
extend(
|
||||
"examples" = [
|
||||
An IP address, or a DNS name, or a Tor onion name, or an I2P address,\
|
||||
followed by the symbol ':', followed by a TCP port number.",
|
||||
extend("examples" = [
|
||||
"xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||
"f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
|
||||
"seed.example.com:8776",
|
||||
"192.0.2.0:31337",
|
||||
],
|
||||
|
|
@ -517,6 +517,12 @@ impl Address {
|
|||
matches!(self.0.host, HostName::Tor(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`HostName`] is an I2P address.
|
||||
#[cfg(feature = "i2p")]
|
||||
pub fn is_i2p(&self) -> bool {
|
||||
matches!(self.0.host, HostName::I2p(_))
|
||||
}
|
||||
|
||||
/// Return the port number of the [`Address`].
|
||||
pub fn port(&self) -> u16 {
|
||||
self.0.port
|
||||
|
|
@ -537,6 +543,8 @@ impl Address {
|
|||
.collect::<String>();
|
||||
format!("{start}…{end}")
|
||||
}
|
||||
#[cfg(feature = "i2p")]
|
||||
HostName::I2p(i2p) => i2p.to_string(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,8 @@ pub enum AddressType {
|
|||
Dns = 3,
|
||||
#[cfg(feature = "tor")]
|
||||
Onion = 4,
|
||||
#[cfg(feature = "i2p")]
|
||||
I2p = 5,
|
||||
}
|
||||
|
||||
impl From<AddressType> for u8 {
|
||||
|
|
@ -219,6 +221,8 @@ impl From<&Address> for AddressType {
|
|||
HostName::Dns(_) => AddressType::Dns,
|
||||
#[cfg(feature = "tor")]
|
||||
HostName::Tor(_) => AddressType::Onion,
|
||||
#[cfg(feature = "i2p")]
|
||||
HostName::I2p(_) => AddressType::I2p,
|
||||
_ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
|
||||
}
|
||||
}
|
||||
|
|
@ -234,6 +238,8 @@ impl TryFrom<u8> for AddressType {
|
|||
3 => Ok(AddressType::Dns),
|
||||
#[cfg(feature = "tor")]
|
||||
4 => Ok(AddressType::Onion),
|
||||
#[cfg(feature = "i2p")]
|
||||
5 => Ok(AddressType::I2p),
|
||||
_ => Err(other),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -537,6 +537,8 @@ impl TryFrom<&sql::Value> for AddressType {
|
|||
"dns" => Ok(AddressType::Dns),
|
||||
#[cfg(feature = "tor")]
|
||||
"onion" => Ok(AddressType::Onion),
|
||||
#[cfg(feature = "i2p")]
|
||||
"i2p" => Ok(AddressType::I2p),
|
||||
_ => Err(err),
|
||||
},
|
||||
_ => Err(err),
|
||||
|
|
@ -552,6 +554,8 @@ impl sql::BindableWithIndex for AddressType {
|
|||
Self::Dns => "dns".bind(stmt, i),
|
||||
#[cfg(feature = "tor")]
|
||||
Self::Onion => "onion".bind(stmt, i),
|
||||
#[cfg(feature = "i2p")]
|
||||
Self::I2p => "i2p".bind(stmt, i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ pub struct RateLimits {
|
|||
schemars(description = "\
|
||||
A node address to connect to. Format: An Ed25519 public key in multibase encoding, \
|
||||
followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion \
|
||||
name, followed by the symbol ':', followed by a TCP port number.\
|
||||
name, or an I2P address, followed by the symbol ':', followed by a TCP port number.\
|
||||
")
|
||||
)]
|
||||
pub struct ConnectAddress(
|
||||
|
|
@ -289,6 +289,7 @@ pub struct ConnectAddress(
|
|||
extend("examples" = [
|
||||
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@rosa.radicle.xyz:8776",
|
||||
"z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
|
||||
"z6Mkvky2mnSYCTUMKRdAUoZXBXLLKtnWEkWeYQcGjjnmobAU@f2atcc7udeub5kh4nkljtjwyk7ikjviorufzgwnfwhkphljl3vhq.b32.i2p:8776",
|
||||
"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi@seed.example.com:8776",
|
||||
"z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5@192.0.2.0:31337",
|
||||
]),
|
||||
|
|
@ -358,7 +359,7 @@ pub enum Relay {
|
|||
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "mode")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[cfg(feature = "tor")]
|
||||
#[cfg(any(feature = "i2p", feature = "tor"))]
|
||||
pub enum AddressConfig {
|
||||
/// Proxy connections to this address type.
|
||||
Proxy {
|
||||
|
|
@ -561,6 +562,10 @@ pub struct Config {
|
|||
deserialize_with = "crate::serde_ext::null_to_default"
|
||||
)]
|
||||
pub onion: AddressConfig,
|
||||
/// I2P address config.
|
||||
#[cfg(feature = "i2p")]
|
||||
#[serde(default, skip_serializing_if = "crate::serde_ext::is_default")]
|
||||
pub i2p: AddressConfig,
|
||||
/// Peer-to-peer network.
|
||||
#[serde(default)]
|
||||
pub network: Network,
|
||||
|
|
@ -618,6 +623,8 @@ impl Config {
|
|||
proxy: None,
|
||||
#[cfg(feature = "tor")]
|
||||
onion: AddressConfig::Drop,
|
||||
#[cfg(feature = "i2p")]
|
||||
i2p: AddressConfig::Drop,
|
||||
relay: Relay::default(),
|
||||
limits: Limits::default(),
|
||||
workers: Workers::default(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use std::str::FromStr;
|
|||
use std::{iter, net};
|
||||
|
||||
use crypto::PublicKey;
|
||||
#[cfg(feature = "i2p")]
|
||||
use cyphernet::addr::i2p::I2pAddr;
|
||||
#[cfg(feature = "tor")]
|
||||
use cyphernet::{EcPk, addr::tor::OnionAddrV3};
|
||||
use qcheck::Arbitrary;
|
||||
|
|
@ -208,10 +210,14 @@ impl Arbitrary for MockRepository {
|
|||
|
||||
impl Arbitrary for AddressType {
|
||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
#[cfg(not(feature = "tor"))]
|
||||
let types = [1, 2, 3];
|
||||
#[allow(unused_mut)]
|
||||
let mut types = vec![1, 2, 3];
|
||||
|
||||
#[cfg(feature = "tor")]
|
||||
let types = [1, 2, 3, 4];
|
||||
types.push(4);
|
||||
|
||||
#[cfg(feature = "i2p")]
|
||||
types.push(5);
|
||||
|
||||
let t = *g.choose(&types).unwrap() as u8;
|
||||
|
||||
|
|
@ -238,11 +244,40 @@ impl Arbitrary for Address {
|
|||
AddressType::Onion => {
|
||||
let pk = PublicKey::arbitrary(g);
|
||||
let addr = OnionAddrV3::from(
|
||||
cyphernet::ed25519::PublicKey::from_pk_compressed(pk.to_byte_array())
|
||||
.unwrap(),
|
||||
cyphernet::ed25519::PublicKey::from_pk_compressed(pk.to_byte_array()).unwrap(),
|
||||
);
|
||||
cyphernet::addr::HostName::Tor(addr)
|
||||
}
|
||||
#[cfg(feature = "i2p")]
|
||||
AddressType::I2p => {
|
||||
let address = if bool::arbitrary(g) {
|
||||
let name: String = iter::repeat_with(|| {
|
||||
char::from(
|
||||
// Base32 alphabet from RFC 4648.
|
||||
*g.choose(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
|
||||
.expect("alphabet is non-empty"),
|
||||
)
|
||||
})
|
||||
.take(56)
|
||||
.collect();
|
||||
|
||||
name + ".b32"
|
||||
} else {
|
||||
g.choose(&["iris.radicle.example", "rosa.radicle.example"])
|
||||
.unwrap()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let suffix = if bool::arbitrary(g) {
|
||||
".i2p"
|
||||
} else {
|
||||
".i2p.alt"
|
||||
};
|
||||
|
||||
let address = address + suffix;
|
||||
|
||||
cyphernet::addr::HostName::I2p(I2pAddr::from_str(&address).unwrap())
|
||||
}
|
||||
};
|
||||
|
||||
Address::from(cyphernet::addr::NetAddr {
|
||||
|
|
|
|||
Loading…
Reference in New Issue