//! Facilities to construct Consensus objects. //! //! (These are only for testing right now, since we don't yet //! support signing or encoding.) use super::rs::build::RouterStatusBuilder; use super::{ CommonHeader, Consensus, ConsensusFlavor, ConsensusHeader, ConsensusVoterInfo, DirSource, Footer, Lifetime, NetParams, ProtoStatus, RouterStatus, SharedRandVal, }; use crate::{BuildError as Error, BuildResult as Result}; use tor_llcrypto::pk::rsa::RsaIdentity; use tor_protover::Protocols; use std::net::IpAddr; /// A builder object used to construct a consensus. /// /// Create one of these with the [`Consensus::builder`] method. /// /// This facility is only enabled when the crate is built with /// the `build_docs` feature. #[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))] pub struct ConsensusBuilder { /// See [`CommonHeader::flavor`] flavor: ConsensusFlavor, /// See [`CommonHeader::lifetime`] lifetime: Option, /// See [`CommonHeader::client_versions`] client_versions: Vec, /// See [`CommonHeader::relay_versions`] relay_versions: Vec, /// See [`CommonHeader::client_protos`] client_protos: ProtoStatus, /// See [`CommonHeader::relay_protos`] relay_protos: ProtoStatus, /// See [`CommonHeader::params`] params: NetParams, /// See [`CommonHeader::voting_delay`] voting_delay: Option<(u32, u32)>, /// See [`ConsensusHeader::consensus_method`] consensus_method: Option, /// See [`ConsensusHeader::shared_rand_prev`] shared_rand_prev: Option, /// See [`ConsensusHeader::shared_rand_cur`] shared_rand_cur: Option, /// See [`Consensus::voters`] voters: Vec, /// See [`Consensus::relays`] relays: Vec, /// See [`Footer::weights`] weights: NetParams, } impl ConsensusBuilder { /// Construct a new ConsensusBuilder object. pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder { ConsensusBuilder { flavor, lifetime: None, client_versions: Vec::new(), relay_versions: Vec::new(), client_protos: ProtoStatus::default(), relay_protos: ProtoStatus::default(), params: NetParams::new(), voting_delay: None, consensus_method: None, shared_rand_prev: None, shared_rand_cur: None, voters: Vec::new(), relays: Vec::new(), weights: NetParams::new(), } } /// Set the lifetime of this consensus. /// /// This value is required. pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self { self.lifetime = Some(lifetime); self } /// Add a single recommended Tor client version to this consensus. /// /// These values are optional for testing. pub fn add_client_version(&mut self, ver: String) -> &mut Self { self.client_versions.push(ver); self } /// Add a single recommended Tor relay version to this consensus. /// /// These values are optional for testing. pub fn add_relay_version(&mut self, ver: String) -> &mut Self { self.relay_versions.push(ver); self } /// Set the required client protocol versions for this consensus. /// /// This value defaults to "no protocol versions required." pub fn required_client_protos(&mut self, protos: Protocols) -> &mut Self { self.client_protos.required = protos; self } /// Set the recommended client protocol versions for this consensus. /// /// This value defaults to "no protocol versions recommended." pub fn recommended_client_protos(&mut self, protos: Protocols) -> &mut Self { self.client_protos.recommended = protos; self } /// Set the required relay protocol versions for this consensus. /// /// This value defaults to "no protocol versions required." pub fn required_relay_protos(&mut self, protos: Protocols) -> &mut Self { self.relay_protos.required = protos; self } /// Set the recommended client protocol versions for this consensus. /// /// This value defaults to "no protocol versions recommended." pub fn recommended_relay_protos(&mut self, protos: Protocols) -> &mut Self { self.relay_protos.recommended = protos; self } /// Set the value for a given consensus parameter by name. pub fn param(&mut self, param: S, val: i32) -> &mut Self where S: Into, { self.params.set(param.into(), val); self } /// Set the voting delays (in seconds) for this consensus. pub fn voting_delay(&mut self, vote_delay: u32, signature_delay: u32) -> &mut Self { self.voting_delay = Some((vote_delay, signature_delay)); self } /// Set the declared consensus method for this consensus. /// /// This value is required. pub fn consensus_method(&mut self, consensus_method: u32) -> &mut Self { self.consensus_method = Some(consensus_method); self } /// Set the previous day's shared-random value for this consensus. /// /// This value is optional. pub fn shared_rand_prev(&mut self, n_reveals: u8, value: Vec) -> &mut Self { self.shared_rand_prev = Some(SharedRandVal { n_reveals, value }); self } /// Set the current day's shared-random value for this consensus. /// /// This value is optional. pub fn shared_rand_cur(&mut self, n_reveals: u8, value: Vec) -> &mut Self { self.shared_rand_cur = Some(SharedRandVal { n_reveals, value }); self } /// Set a named weight parameter for this consensus. pub fn weight(&mut self, param: S, val: i32) -> &mut Self where S: Into, { self.weights.set(param.into(), val); self } /// Replace all weight parameters for this consensus. pub fn weights(&mut self, weights: NetParams) -> &mut Self { self.weights = weights; self } /// Create a VoterInfoBuilder to add a voter to this builder. /// /// In theory these are required, but nothing asks for them. pub fn voter(&self) -> VoterInfoBuilder { VoterInfoBuilder::new() } /// Insert a single routerstatus into this builder. pub(crate) fn add_rs(&mut self, rs: RS) -> &mut Self { self.relays.push(rs); self } } impl ConsensusBuilder { /// Create a RouterStatusBuilder to add a RouterStatus to this builder. /// /// You can make a consensus with no RouterStatus entries, but it /// won't actually be good for anything. pub fn rs(&self) -> RouterStatusBuilder { RouterStatusBuilder::new() } /// Try to create a consensus object from this builder. /// /// This object might not have all of the data that a valid /// consensus would have. Therefore, it should only be used for /// testing. pub fn testing_consensus(&self) -> Result> { let lifetime = self .lifetime .as_ref() .ok_or(Error::CannotBuild("Missing lifetime."))? .clone(); let hdr = CommonHeader { flavor: self.flavor, lifetime, client_versions: self.client_versions.clone(), relay_versions: self.relay_versions.clone(), client_protos: self.client_protos.clone(), relay_protos: self.relay_protos.clone(), params: self.params.clone(), voting_delay: self.voting_delay, }; let consensus_method = self .consensus_method .ok_or(Error::CannotBuild("Missing consensus method."))?; let header = ConsensusHeader { hdr, consensus_method, shared_rand_prev: self.shared_rand_prev.clone(), shared_rand_cur: self.shared_rand_cur.clone(), }; let footer = Footer { weights: self.weights.clone(), }; let mut relays = self.relays.clone(); relays.sort_by_key(|r| *r.rsa_identity()); // TODO: check for duplicates? Ok(Consensus { header, voters: self.voters.clone(), relays, footer, }) } } /// Builder object for constructing a [`ConsensusVoterInfo`] pub struct VoterInfoBuilder { /// See [`DirSource::nickname`] nickname: Option, /// See [`DirSource::identity`] identity: Option, /// See [`DirSource::ip`] ip: Option, /// See [`ConsensusVoterInfo::contact`] contact: Option, /// See [`ConsensusVoterInfo::vote_digest`] vote_digest: Vec, /// See [`DirSource::or_port`] or_port: u16, /// See [`DirSource::dir_port`] dir_port: u16, } impl VoterInfoBuilder { /// Construct a new 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, } } /// Set a nickname. /// /// This value is required. pub fn nickname(&mut self, nickname: String) -> &mut Self { self.nickname = Some(nickname); self } /// Set an RSA identity. /// /// This value is required. pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self { self.identity = Some(identity); self } /// Set a IP-valued address. /// /// This value is required. pub fn ip(&mut self, ip: IpAddr) -> &mut Self { self.ip = Some(ip); self } /// Set a contact line for this voter. /// /// This value is optional. pub fn contact(&mut self, contact: String) -> &mut Self { self.contact = Some(contact); self } /// Set the declared vote digest for this voter within a consensus. /// /// This value is required. pub fn vote_digest(&mut self, vote_digest: Vec) -> &mut Self { self.vote_digest = vote_digest; self } /// Set the declared OrPort for this voter. pub fn or_port(&mut self, or_port: u16) -> &mut Self { self.or_port = or_port; self } /// Set the declared DirPort for this voter. pub fn dir_port(&mut self, dir_port: u16) -> &mut Self { self.dir_port = dir_port; self } /// Add the voter that we've been building into the in-progress /// consensus of `builder`. 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::unwrap_used)] use super::*; use crate::doc::netstatus::RelayFlags; use std::net::SocketAddr; use std::time::{Duration, SystemTime}; #[test] fn consensus() { let now = SystemTime::now(); 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, (*b"").into()) .shared_rand_cur(1, (*b"hi there").into()) .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(RelayFlags::FAST) .add_flags(RelayFlags::STABLE | RelayFlags::V2DIR) .version("Arti 0.0.0".into()) .protos("DirCache=7".parse().unwrap()) .build_into(&mut builder) .unwrap(); let _cons = builder.testing_consensus().unwrap(); // TODO: Check actual members of `cons` above. } }