Add a consensus builder, for testing.
This commit is contained in:
parent
b47b9a6f54
commit
29b921b0e5
|
@ -15,7 +15,7 @@ use tor_llcrypto::pk::rsa;
|
|||
///
|
||||
/// Create one of these with the [`AuthCert::builder`] method.
|
||||
///
|
||||
/// This facility is only enabled when the craet is built with
|
||||
/// This facility is only enabled when the crate is built with
|
||||
/// the `build_docs` feature.
|
||||
pub struct AuthCertBuilder {
|
||||
/// See [`AuthCert::address`]
|
||||
|
|
|
@ -60,6 +60,7 @@ pub struct Microdesc {
|
|||
// correlate the microdesc to a consensus, it's never used again.
|
||||
sha256: MdDigest,
|
||||
/// Public key used for the deprecated TAP circuit extension protocol.
|
||||
// TODO: why even store this? Nothing in Arti will ever use it.
|
||||
tap_onion_key: rsa::PublicKey,
|
||||
/// Public key used for the ntor circuit extension protocol.
|
||||
ntor_onion_key: curve25519::PublicKey,
|
||||
|
|
|
@ -43,10 +43,11 @@
|
|||
//! As with the other tor-netdoc types, I'm deferring those till I know what
|
||||
//! they should be.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod rs;
|
||||
|
||||
#[cfg(feature = "build_docs")]
|
||||
mod build;
|
||||
|
||||
use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
|
||||
use crate::parse::keyword::Keyword;
|
||||
use crate::parse::parser::{Section, SectionRules};
|
||||
|
@ -67,6 +68,8 @@ use tor_llcrypto::pk::rsa::RsaIdentity;
|
|||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
#[cfg(feature = "build_docs")]
|
||||
pub use build::ConsensusBuilder;
|
||||
pub use rs::MdConsensusRouterStatus;
|
||||
pub use rs::NsConsensusRouterStatus;
|
||||
|
||||
|
@ -142,6 +145,13 @@ pub struct NetParams<T> {
|
|||
}
|
||||
|
||||
impl<T> NetParams<T> {
|
||||
/// Create a new empty list of NetParams.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn new() -> Self {
|
||||
NetParams {
|
||||
params: HashMap::new(),
|
||||
}
|
||||
}
|
||||
/// Retrieve a given network parameter, if it is present.
|
||||
pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
|
||||
self.params.get(v.as_ref())
|
||||
|
@ -150,6 +160,11 @@ impl<T> NetParams<T> {
|
|||
pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
|
||||
self.params.iter()
|
||||
}
|
||||
/// Set or replace the value of a network parameter.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn set(&mut self, k: String, v: T) {
|
||||
self.params.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for NetParams<T>
|
||||
|
@ -167,7 +182,7 @@ where
|
|||
|
||||
/// A list of subprotocol versions that implementors should/must provide.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProtoStatus {
|
||||
/// Set of protocols that are recommended; if we're missing a protocol
|
||||
/// in this list we should warn the user.
|
||||
|
@ -280,8 +295,8 @@ struct CommonHeader {
|
|||
/// network should operator. Some of these adjust timeouts and
|
||||
/// whatnot; some features things on and off.
|
||||
params: NetParams<i32>,
|
||||
/// How long should voters wait for votes and consensuses to
|
||||
/// propagate?
|
||||
/// How long in seconds should voters wait for votes and
|
||||
/// signatures (respectively) to propagate?
|
||||
voting_delay: Option<(u32, u32)>,
|
||||
}
|
||||
|
||||
|
@ -378,7 +393,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
/// Recognized weight fields on a single relay in a consensus
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum RouterWeight {
|
||||
// TODO SPEC: Document that these are u32 in dir-spec.txt
|
||||
|
@ -410,7 +425,7 @@ struct ConsensusVoterInfo {
|
|||
dir_source: DirSource,
|
||||
/// Human-readable contact information about the authority
|
||||
contact: String,
|
||||
/// Digest of the vote that the auuthority cast to contribute to
|
||||
/// Digest of the vote that the authority cast to contribute to
|
||||
/// this consensus.
|
||||
vote_digest: Vec<u8>,
|
||||
}
|
||||
|
@ -445,7 +460,7 @@ pub trait ParseRouterStatus: Sized + Sealed {
|
|||
/// Not implementable outside of the `tor-netdoc` crate.
|
||||
pub trait RouterStatus: Sealed {
|
||||
/// A digest of the document that's identified by this RouterStatus.
|
||||
type DocumentDigest;
|
||||
type DocumentDigest: Clone;
|
||||
|
||||
/// Return RSA identity for the relay described by this RouterStatus
|
||||
fn rsa_identity(&self) -> &RsaIdentity;
|
||||
|
@ -525,6 +540,7 @@ decl_keyword! {
|
|||
// ParseRouterStatus crate. But I'd rather find a way to make it
|
||||
// private.
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub NetstatusKwd {
|
||||
// Header
|
||||
"network-status-version" => NETWORK_STATUS_VERSION,
|
||||
|
@ -1133,6 +1149,15 @@ impl Signature {
|
|||
pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
|
||||
|
||||
impl<RS: ParseRouterStatus + RouterStatus> Consensus<RS> {
|
||||
/// Return a new ConsensusBuilder for building test consensus objects.
|
||||
///
|
||||
/// This function is only available when the `build_docs` feature has
|
||||
/// been enabled.
|
||||
#[cfg(feature = "build_docs")]
|
||||
pub fn builder() -> ConsensusBuilder<RS> {
|
||||
ConsensusBuilder::new(RS::flavor())
|
||||
}
|
||||
|
||||
/// Try to parse a single networkstatus document from a string.
|
||||
pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
|
||||
let mut reader = NetDocReader::new(s);
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
//! 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::{Error, 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.
|
||||
pub struct ConsensusBuilder<RS> {
|
||||
/// See [`CommonHeader::flavor`]
|
||||
flavor: ConsensusFlavor,
|
||||
/// See [`CommonHeader::lifetime`]
|
||||
lifetime: Option<Lifetime>,
|
||||
/// See [`CommonHeader::client_versions`]
|
||||
client_versions: Vec<String>,
|
||||
/// See [`CommonHeader::relay_versions`]
|
||||
relay_versions: Vec<String>,
|
||||
/// See [`CommonHeader::client_protos`]
|
||||
client_protos: ProtoStatus,
|
||||
/// See [`CommonHeader::relay_protos`]
|
||||
relay_protos: ProtoStatus,
|
||||
/// See [`CommonHeader::params`]
|
||||
params: NetParams<i32>,
|
||||
/// See [`CommonHeader::voting_delay`]
|
||||
voting_delay: Option<(u32, u32)>,
|
||||
/// See [`ConsensusHeader::consensus_method`]
|
||||
consensus_method: Option<u32>,
|
||||
/// See [`ConsensusHeader::shared_rand_prev`]
|
||||
shared_rand_prev: Option<SharedRandVal>,
|
||||
/// See [`ConsensusHeader::shared_rand_cur`]
|
||||
shared_rand_cur: Option<SharedRandVal>,
|
||||
/// See [`Consensus::voters`]
|
||||
voters: Vec<ConsensusVoterInfo>,
|
||||
/// See [`Consensus::routers`]
|
||||
routers: Vec<RS>,
|
||||
/// See [`Footer::weights`]
|
||||
weights: NetParams<i32>,
|
||||
}
|
||||
|
||||
impl<RS> ConsensusBuilder<RS> {
|
||||
/// Construct a new ConsensusBuilder object.
|
||||
pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder<RS> {
|
||||
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(),
|
||||
routers: 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<S>(&mut self, param: S, val: i32) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<u8>) -> &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<u8>) -> &mut Self {
|
||||
self.shared_rand_cur = Some(SharedRandVal { n_reveals, value });
|
||||
self
|
||||
}
|
||||
/// Set a named weight parameter for this consensus.
|
||||
pub fn weight<S>(&mut self, param: S, val: i32) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.weights.set(param.into(), val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a VoterInfoBuilder to add a voter to this builder.
|
||||
///
|
||||
/// In theory these are requied, 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.routers.push(rs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<RS: RouterStatus + Clone> ConsensusBuilder<RS> {
|
||||
/// 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<RS::DocumentDigest> {
|
||||
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<Consensus<RS>> {
|
||||
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 routers = self.routers.clone();
|
||||
routers.sort_by_key(|r| *r.rsa_identity());
|
||||
// TODO: check for duplicates?
|
||||
|
||||
Ok(Consensus {
|
||||
header,
|
||||
voters: self.voters.clone(),
|
||||
routers,
|
||||
footer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder object for constructing a [`ConsensusVoterInfo`]
|
||||
pub struct VoterInfoBuilder {
|
||||
/// See [`DirSource::nickname`]
|
||||
nickname: Option<String>,
|
||||
/// See [`DirSource::identity`]
|
||||
identity: Option<RsaIdentity>,
|
||||
/// See [`DirSource::address`]
|
||||
address: Option<String>,
|
||||
/// See [`DirSource::ip`]
|
||||
ip: Option<IpAddr>,
|
||||
/// See [`ConsensusVoterInfo::contact`]
|
||||
contact: Option<String>,
|
||||
/// See [`ConsensusVoterInfo::vote_digest`]
|
||||
vote_digest: Vec<u8>,
|
||||
/// 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,
|
||||
address: 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 string-valued address.
|
||||
///
|
||||
/// This value is required.
|
||||
pub fn address(&mut self, address: String) -> &mut Self {
|
||||
self.address = Some(address);
|
||||
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<u8>) -> &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<RS>(&self, builder: &mut ConsensusBuilder<RS>) -> 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 address = self
|
||||
.address
|
||||
.as_ref()
|
||||
.ok_or(Error::CannotBuild("Missing address"))?
|
||||
.clone();
|
||||
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,
|
||||
address,
|
||||
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 {
|
||||
use super::*;
|
||||
use crate::doc::netstatus::RouterFlags;
|
||||
|
||||
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())
|
||||
.address("fuzzy.example.com".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())
|
||||
.published(now - one_hour * 6)
|
||||
.add_or_port("10.0.0.60:9100".parse().unwrap())
|
||||
.add_or_port("[f00f::1]:9200".parse().unwrap())
|
||||
.dir_port(66)
|
||||
.doc_digest([99; 32])
|
||||
.set_flags(RouterFlags::FAST)
|
||||
.add_flags(RouterFlags::STABLE | RouterFlags::V2DIR)
|
||||
.version("Arti 0.0.0".into())
|
||||
.protos("DirCache=7".parse().unwrap())
|
||||
.build(&mut builder)
|
||||
.unwrap();
|
||||
|
||||
let _cons = builder.testing_consensus().unwrap();
|
||||
|
||||
// TODO: Check actual members of `cons` above.
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@
|
|||
//! This is a private module; relevant pieces are re-exported by its
|
||||
//! parent.
|
||||
|
||||
#[cfg(feature = "build_docs")]
|
||||
pub(crate) mod build;
|
||||
|
||||
use super::{
|
||||
ConsensusFlavor, NetstatusKwd, ParseRouterStatus, RouterFlags, RouterStatus, RouterWeight,
|
||||
};
|
||||
|
@ -38,6 +41,18 @@ pub struct NsConsensusRouterStatus {
|
|||
rs: GenericRouterStatus<RdDigest>,
|
||||
}
|
||||
|
||||
impl From<GenericRouterStatus<RdDigest>> for NsConsensusRouterStatus {
|
||||
fn from(rs: GenericRouterStatus<RdDigest>) -> Self {
|
||||
NsConsensusRouterStatus { rs }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GenericRouterStatus<MdDigest>> for MdConsensusRouterStatus {
|
||||
fn from(rs: GenericRouterStatus<MdDigest>) -> Self {
|
||||
MdConsensusRouterStatus { rs }
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared implementation of MdConsensusRouterStatus and NsConsensusRouterStatus.
|
||||
#[derive(Debug, Clone)]
|
||||
struct GenericRouterStatus<D> {
|
||||
|
@ -53,6 +68,7 @@ struct GenericRouterStatus<D> {
|
|||
///
|
||||
/// This value should be ignored for all purposes; see
|
||||
/// [proposal 275](https://gitlab.torproject.org/tpo/core/torspec/-/blob/master/proposals/275-md-published-time-is-silly.txt).
|
||||
// TODO: so why not remove this?
|
||||
published: time::SystemTime,
|
||||
/// A list of address:port values where this relay can be reached.
|
||||
addrs: Vec<net::SocketAddr>,
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
//! Provide builder functionality for routerstatuses.
|
||||
|
||||
use super::{GenericRouterStatus, MdConsensusRouterStatus, NsConsensusRouterStatus};
|
||||
use crate::doc::microdesc::MdDigest;
|
||||
use crate::doc::netstatus::{ConsensusBuilder, RouterFlags, RouterWeight};
|
||||
use crate::doc::routerdesc::RdDigest;
|
||||
use crate::{Error, Result};
|
||||
use tor_llcrypto::pk::rsa::RsaIdentity;
|
||||
use tor_protover::Protocols;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// A Builder object for creating a RouterStatus and adding it to a
|
||||
/// consensus.
|
||||
pub struct RouterStatusBuilder<D> {
|
||||
/// See [`GenericRouterStatus::nickname`].
|
||||
nickname: Option<String>,
|
||||
/// See [`GenericRouterStatus::identity`].
|
||||
identity: Option<RsaIdentity>,
|
||||
/// See [`GenericRouterStatus::published`].
|
||||
published: Option<SystemTime>,
|
||||
/// See [`GenericRouterStatus::addrs`].
|
||||
addrs: Vec<SocketAddr>,
|
||||
/// See [`GenericRouterStatus::dir_port`].
|
||||
dir_port: u16, // never used, I think? XXXX
|
||||
/// See [`GenericRouterStatus::doc_digest`].
|
||||
doc_digest: Option<D>,
|
||||
/// See [`GenericRouterStatus::flags`].
|
||||
flags: RouterFlags,
|
||||
/// See [`GenericRouterStatus::versions`].
|
||||
version: Option<String>,
|
||||
/// See [`GenericRouterStatus::protos`].
|
||||
protos: Option<Protocols>,
|
||||
/// See [`GenericRouterStatus::weight`].
|
||||
weight: Option<RouterWeight>,
|
||||
}
|
||||
|
||||
impl<D: Clone> RouterStatusBuilder<D> {
|
||||
/// Construct a new RouterStatusBuilder.
|
||||
pub(crate) fn new() -> Self {
|
||||
RouterStatusBuilder {
|
||||
nickname: None,
|
||||
identity: None,
|
||||
published: None,
|
||||
addrs: Vec::new(),
|
||||
dir_port: 0,
|
||||
doc_digest: None,
|
||||
flags: RouterFlags::empty(),
|
||||
version: None,
|
||||
protos: None,
|
||||
weight: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the nickname for this router.
|
||||
///
|
||||
/// This value defaults to "Unnamed".
|
||||
pub fn nickname(&mut self, nickname: String) -> &mut Self {
|
||||
self.nickname = Some(nickname);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the RSA identity for this router.
|
||||
///
|
||||
/// (The Ed25519 identity is in the microdescriptor).
|
||||
///
|
||||
/// This value is required.
|
||||
pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
|
||||
self.identity = Some(identity);
|
||||
self
|
||||
}
|
||||
/// Set the publication time for this router.
|
||||
///
|
||||
/// This value is optional, and does nothing (TODO).
|
||||
pub fn published(&mut self, published: SystemTime) -> &mut Self {
|
||||
self.published = Some(published);
|
||||
self
|
||||
}
|
||||
/// Add an OrPort at `addr` to this router.
|
||||
///
|
||||
/// At least one value here is required.
|
||||
pub fn add_or_port(&mut self, addr: SocketAddr) -> &mut Self {
|
||||
self.addrs.push(addr);
|
||||
self
|
||||
}
|
||||
/// Set a directory port for this router.
|
||||
///
|
||||
/// Nothing in Arti uses this value; it defaults to 0.
|
||||
pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
|
||||
self.dir_port = dir_port;
|
||||
self
|
||||
}
|
||||
/// Set the document digest for this router.
|
||||
///
|
||||
/// This value is required.
|
||||
pub fn doc_digest(&mut self, doc_digest: D) -> &mut Self {
|
||||
self.doc_digest = Some(doc_digest);
|
||||
self
|
||||
}
|
||||
/// Replace the current flags in this router with `flags`.
|
||||
pub fn set_flags(&mut self, flags: RouterFlags) -> &mut Self {
|
||||
self.flags = flags;
|
||||
self
|
||||
}
|
||||
/// Make all the flags in `flags` become set on this router,
|
||||
/// in addition to the flags already set.
|
||||
pub fn add_flags(&mut self, flags: RouterFlags) -> &mut Self {
|
||||
self.flags |= flags;
|
||||
self
|
||||
}
|
||||
/// Set the version of this router.
|
||||
///
|
||||
/// This value is optional.
|
||||
pub fn version(&mut self, version: String) -> &mut Self {
|
||||
self.version = Some(version);
|
||||
self
|
||||
}
|
||||
/// Set the list of subprotocols supported by this router.
|
||||
///
|
||||
/// This value is required.
|
||||
pub fn protos(&mut self, protos: Protocols) -> &mut Self {
|
||||
self.protos = Some(protos);
|
||||
self
|
||||
}
|
||||
/// Try to build a GenericRouterStatus from this builder.
|
||||
fn finish(&self) -> Result<GenericRouterStatus<D>> {
|
||||
let nickname = self.nickname.clone().unwrap_or_else(|| "Unnamed".into());
|
||||
let identity = self
|
||||
.identity
|
||||
.ok_or(Error::CannotBuild("Missing RSA identity"))?;
|
||||
let published = self.published.unwrap_or_else(SystemTime::now);
|
||||
if self.addrs.is_empty() {
|
||||
return Err(Error::CannotBuild("No addresses"));
|
||||
}
|
||||
let or_port = self.addrs[0].port();
|
||||
let doc_digest = self
|
||||
.doc_digest
|
||||
.as_ref()
|
||||
.ok_or(Error::CannotBuild("Missing document digest"))?
|
||||
.clone();
|
||||
let protos = self
|
||||
.protos
|
||||
.as_ref()
|
||||
.ok_or(Error::CannotBuild("Missing protocols"))?
|
||||
.clone();
|
||||
let weight = self.weight.unwrap_or(RouterWeight::Unmeasured(0));
|
||||
|
||||
Ok(GenericRouterStatus {
|
||||
nickname,
|
||||
identity,
|
||||
published,
|
||||
addrs: self.addrs.clone(),
|
||||
or_port,
|
||||
dir_port: self.dir_port,
|
||||
doc_digest,
|
||||
version: self.version.clone(),
|
||||
protos,
|
||||
flags: self.flags,
|
||||
weight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RouterStatusBuilder<RdDigest> {
|
||||
/// Try to finish this builder and add its RouterStatus to a
|
||||
/// provided ConsensusBuilder.
|
||||
pub fn build(&self, builder: &mut ConsensusBuilder<NsConsensusRouterStatus>) -> Result<()> {
|
||||
let rs = self.finish()?;
|
||||
builder.add_rs(rs.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RouterStatusBuilder<MdDigest> {
|
||||
/// Try to finish this builder and add its RouterStatus to a
|
||||
/// provided ConsensusBuilder.
|
||||
pub fn build(&self, builder: &mut ConsensusBuilder<MdConsensusRouterStatus>) -> Result<()> {
|
||||
let rs = self.finish()?;
|
||||
builder.add_rs(rs.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue