ns_use_this_variety! {
use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
use [crate::doc::netstatus::rs]::?::{RouterStatus};
}
#[cfg(not(doc))]
ns_use_this_variety! {
use [crate::doc::netstatus]::?::{Consensus, Preamble};
}
#[cfg(doc)]
ns_use_this_variety! {
pub use [crate::doc::netstatus]::?::{Consensus, Preamble};
}
use super::*;
#[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))]
pub struct ConsensusBuilder {
flavor: ConsensusFlavor,
lifetime: Option<Lifetime>,
client_versions: Vec<String>,
server_versions: Vec<String>,
client_protos: ProtoStatus,
relay_protos: ProtoStatus,
params: NetParams<i32>,
voting_delay: Option<(u32, u32)>,
consensus_method: Option<u32>,
shared_rand_previous_value: Option<SharedRandStatus>,
shared_rand_current_value: Option<SharedRandStatus>,
voters: Vec<ConsensusVoterInfo>,
relays: Vec<RouterStatus>,
weights: NetParams<i32>,
}
impl ConsensusBuilder {
pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder {
ConsensusBuilder {
flavor,
lifetime: None,
client_versions: Vec::new(),
server_versions: Vec::new(),
client_protos: ProtoStatus::default(),
relay_protos: ProtoStatus::default(),
params: NetParams::new(),
voting_delay: None,
consensus_method: None,
shared_rand_previous_value: None,
shared_rand_current_value: None,
voters: Vec::new(),
relays: Vec::new(),
weights: NetParams::new(),
}
}
pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
self.lifetime = Some(lifetime);
self
}
pub fn add_client_version(&mut self, ver: String) -> &mut Self {
self.client_versions.push(ver);
self
}
pub fn add_relay_version(&mut self, ver: String) -> &mut Self {
self.server_versions.push(ver);
self
}
pub fn required_client_protos(&mut self, protos: Protocols) -> &mut Self {
self.client_protos.required = protos;
self
}
pub fn recommended_client_protos(&mut self, protos: Protocols) -> &mut Self {
self.client_protos.recommended = protos;
self
}
pub fn required_relay_protos(&mut self, protos: Protocols) -> &mut Self {
self.relay_protos.required = protos;
self
}
pub fn recommended_relay_protos(&mut self, protos: Protocols) -> &mut Self {
self.relay_protos.recommended = protos;
self
}
pub fn param<S>(&mut self, param: S, val: i32) -> &mut Self
where
S: Into<String>,
{
self.params.set(param.into(), val);
self
}
pub fn voting_delay(&mut self, vote_delay: u32, signature_delay: u32) -> &mut Self {
self.voting_delay = Some((vote_delay, signature_delay));
self
}
pub fn consensus_method(&mut self, consensus_method: u32) -> &mut Self {
self.consensus_method = Some(consensus_method);
self
}
pub fn shared_rand_prev(
&mut self,
n_reveals: u8,
value: SharedRandVal,
timestamp: Option<SystemTime>,
) -> &mut Self {
self.shared_rand_previous_value = Some(SharedRandStatus {
n_reveals,
value,
timestamp: timestamp.map(Iso8601TimeNoSp),
});
self
}
pub fn shared_rand_cur(
&mut self,
n_reveals: u8,
value: SharedRandVal,
timestamp: Option<SystemTime>,
) -> &mut Self {
self.shared_rand_current_value = Some(SharedRandStatus {
n_reveals,
value,
timestamp: timestamp.map(Iso8601TimeNoSp),
});
self
}
pub fn weight<S>(&mut self, param: S, val: i32) -> &mut Self
where
S: Into<String>,
{
self.weights.set(param.into(), val);
self
}
pub fn weights(&mut self, weights: NetParams<i32>) -> &mut Self {
self.weights = weights;
self
}
pub fn voter(&self) -> VoterInfoBuilder {
VoterInfoBuilder::new()
}
pub(crate) fn add_rs(&mut self, rs: RouterStatus) -> &mut Self {
self.relays.push(rs);
self
}
}
impl ConsensusBuilder {
pub fn rs(&self) -> RouterStatusBuilder {
RouterStatusBuilder::new()
}
pub fn testing_consensus(&self) -> Result<Consensus> {
let lifetime = self
.lifetime
.as_ref()
.ok_or(Error::CannotBuild("Missing lifetime."))?
.clone();
let proto_statuses = Arc::new(ProtoStatuses {
client: self.client_protos.clone(),
relay: self.relay_protos.clone(),
});
let consensus_method = self
.consensus_method
.ok_or(Error::CannotBuild("Missing consensus method."))?;
let preamble = Preamble {
lifetime,
client_versions: self.client_versions.clone(),
server_versions: self.server_versions.clone(),
proto_statuses,
params: self.params.clone(),
voting_delay: self.voting_delay,
consensus_method,
consensus_methods: NotPresent,
published: NotPresent,
shared_rand_previous_value: self.shared_rand_previous_value.clone(),
shared_rand_current_value: self.shared_rand_current_value.clone(),
};
let footer = Footer {
weights: self.weights.clone(),
};
let mut relays = self.relays.clone();
relays.sort_by_key(|r| *r.rsa_identity());
Ok(Consensus {
flavor: self.flavor,
preamble,
voters: self.voters.clone(),
relays,
footer,
})
}
}
pub struct VoterInfoBuilder {
nickname: Option<String>,
identity: Option<RsaIdentity>,
ip: Option<IpAddr>,
contact: Option<String>,
vote_digest: Vec<u8>,
or_port: u16,
dir_port: u16,
}
impl VoterInfoBuilder {
pub(crate) fn new() -> Self {
VoterInfoBuilder {
nickname: None,
identity: None,
ip: None,
contact: None,
vote_digest: Vec::new(),
or_port: 0,
dir_port: 0,
}
}
pub fn nickname(&mut self, nickname: String) -> &mut Self {
self.nickname = Some(nickname);
self
}
pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
self.identity = Some(identity);
self
}
pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
self.ip = Some(ip);
self
}
pub fn contact(&mut self, contact: String) -> &mut Self {
self.contact = Some(contact);
self
}
pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
self.vote_digest = vote_digest;
self
}
pub fn or_port(&mut self, or_port: u16) -> &mut Self {
self.or_port = or_port;
self
}
pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
self.dir_port = dir_port;
self
}
pub fn build(&self, builder: &mut ConsensusBuilder) -> Result<()> {
let nickname = self
.nickname
.as_ref()
.ok_or(Error::CannotBuild("Missing nickname"))?
.clone();
let identity = self
.identity
.ok_or(Error::CannotBuild("Missing identity"))?;
let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
let contact = self
.contact
.as_ref()
.ok_or(Error::CannotBuild("Missing contact"))?
.clone();
if self.vote_digest.is_empty() {
return Err(Error::CannotBuild("Missing vote digest"));
}
let dir_source = DirSource {
nickname,
identity,
ip,
dir_port: self.dir_port,
or_port: self.or_port,
};
let info = ConsensusVoterInfo {
dir_source,
contact,
vote_digest: self.vote_digest.clone(),
};
builder.voters.push(info);
Ok(())
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use crate::types::relay_flags::RelayFlag;
use std::net::SocketAddr;
use web_time_compat::{Duration, SystemTime, SystemTimeExt};
#[test]
fn consensus() {
let now = SystemTime::get();
let one_hour = Duration::new(3600, 0);
let mut builder = crate::doc::netstatus::MdConsensus::builder();
builder
.lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
.add_client_version("0.4.5.8".into())
.add_relay_version("0.4.5.9".into())
.required_client_protos("DirCache=2 LinkAuth=3".parse().unwrap())
.required_relay_protos("DirCache=1".parse().unwrap())
.recommended_client_protos("DirCache=6".parse().unwrap())
.recommended_relay_protos("DirCache=5".parse().unwrap())
.param("wombat", 7)
.param("knish", 1212)
.voting_delay(7, 8)
.consensus_method(32)
.shared_rand_prev(1, SharedRandVal([b'x'; 32]), None)
.shared_rand_cur(1, SharedRandVal([b'y'; 32]), None)
.weight("Wxy", 303)
.weight("Wow", 999);
builder
.voter()
.nickname("Fuzzy".into())
.identity([15; 20].into())
.ip("10.0.0.200".parse().unwrap())
.contact("admin@fuzzy.example.com".into())
.vote_digest((*b"1234").into())
.or_port(9001)
.dir_port(9101)
.build(&mut builder)
.unwrap();
builder
.rs()
.nickname("Fred".into())
.identity([155; 20].into())
.add_or_port(SocketAddr::from(([10, 0, 0, 60], 9100)))
.add_or_port("[f00f::1]:9200".parse().unwrap())
.doc_digest([99; 32])
.set_flags(RelayFlag::Fast)
.add_flags(RelayFlag::Stable | RelayFlag::V2Dir)
.version("Arti 0.0.0".into())
.protos("DirCache=7".parse().unwrap())
.build_into(&mut builder)
.unwrap();
let _cons = builder.testing_consensus().unwrap();
}
}