tor_netdoc/doc/netstatus/
build.rs1use super::rs::build::RouterStatusBuilder;
7use super::{
8 CommonHeader, Consensus, ConsensusFlavor, ConsensusHeader, ConsensusVoterInfo, DirSource,
9 Footer, Lifetime, NetParams, ProtoStatus, RouterStatus, SharedRandStatus, SharedRandVal,
10};
11
12use crate::{BuildError as Error, BuildResult as Result};
13use tor_llcrypto::pk::rsa::RsaIdentity;
14use tor_protover::Protocols;
15
16use std::net::IpAddr;
17use std::time::SystemTime;
18
19#[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))]
26pub struct ConsensusBuilder<RS> {
27 flavor: ConsensusFlavor,
29 lifetime: Option<Lifetime>,
31 client_versions: Vec<String>,
33 relay_versions: Vec<String>,
35 client_protos: ProtoStatus,
37 relay_protos: ProtoStatus,
39 params: NetParams<i32>,
41 voting_delay: Option<(u32, u32)>,
43 consensus_method: Option<u32>,
45 shared_rand_prev: Option<SharedRandStatus>,
47 shared_rand_cur: Option<SharedRandStatus>,
49 voters: Vec<ConsensusVoterInfo>,
51 relays: Vec<RS>,
53 weights: NetParams<i32>,
55}
56
57impl<RS> ConsensusBuilder<RS> {
58 pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder<RS> {
60 ConsensusBuilder {
61 flavor,
62 lifetime: None,
63 client_versions: Vec::new(),
64 relay_versions: Vec::new(),
65 client_protos: ProtoStatus::default(),
66 relay_protos: ProtoStatus::default(),
67 params: NetParams::new(),
68 voting_delay: None,
69 consensus_method: None,
70 shared_rand_prev: None,
71 shared_rand_cur: None,
72 voters: Vec::new(),
73 relays: Vec::new(),
74 weights: NetParams::new(),
75 }
76 }
77
78 pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
82 self.lifetime = Some(lifetime);
83 self
84 }
85
86 pub fn add_client_version(&mut self, ver: String) -> &mut Self {
90 self.client_versions.push(ver);
91 self
92 }
93 pub fn add_relay_version(&mut self, ver: String) -> &mut Self {
97 self.relay_versions.push(ver);
98 self
99 }
100 pub fn required_client_protos(&mut self, protos: Protocols) -> &mut Self {
104 self.client_protos.required = protos;
105 self
106 }
107 pub fn recommended_client_protos(&mut self, protos: Protocols) -> &mut Self {
111 self.client_protos.recommended = protos;
112 self
113 }
114 pub fn required_relay_protos(&mut self, protos: Protocols) -> &mut Self {
118 self.relay_protos.required = protos;
119 self
120 }
121 pub fn recommended_relay_protos(&mut self, protos: Protocols) -> &mut Self {
125 self.relay_protos.recommended = protos;
126 self
127 }
128 pub fn param<S>(&mut self, param: S, val: i32) -> &mut Self
130 where
131 S: Into<String>,
132 {
133 self.params.set(param.into(), val);
134 self
135 }
136 pub fn voting_delay(&mut self, vote_delay: u32, signature_delay: u32) -> &mut Self {
138 self.voting_delay = Some((vote_delay, signature_delay));
139 self
140 }
141 pub fn consensus_method(&mut self, consensus_method: u32) -> &mut Self {
145 self.consensus_method = Some(consensus_method);
146 self
147 }
148 pub fn shared_rand_prev(
152 &mut self,
153 n_reveals: u8,
154 value: SharedRandVal,
155 timestamp: Option<SystemTime>,
156 ) -> &mut Self {
157 self.shared_rand_prev = Some(SharedRandStatus {
158 n_reveals,
159 value,
160 timestamp,
161 });
162 self
163 }
164 pub fn shared_rand_cur(
168 &mut self,
169 n_reveals: u8,
170 value: SharedRandVal,
171 timestamp: Option<SystemTime>,
172 ) -> &mut Self {
173 self.shared_rand_cur = Some(SharedRandStatus {
174 n_reveals,
175 value,
176 timestamp,
177 });
178 self
179 }
180 pub fn weight<S>(&mut self, param: S, val: i32) -> &mut Self
182 where
183 S: Into<String>,
184 {
185 self.weights.set(param.into(), val);
186 self
187 }
188 pub fn weights(&mut self, weights: NetParams<i32>) -> &mut Self {
190 self.weights = weights;
191 self
192 }
193 pub fn voter(&self) -> VoterInfoBuilder {
197 VoterInfoBuilder::new()
198 }
199
200 pub(crate) fn add_rs(&mut self, rs: RS) -> &mut Self {
202 self.relays.push(rs);
203 self
204 }
205}
206
207impl<RS: RouterStatus + Clone> ConsensusBuilder<RS> {
208 pub fn rs(&self) -> RouterStatusBuilder<RS::DocumentDigest> {
213 RouterStatusBuilder::new()
214 }
215
216 pub fn testing_consensus(&self) -> Result<Consensus<RS>> {
222 let lifetime = self
223 .lifetime
224 .as_ref()
225 .ok_or(Error::CannotBuild("Missing lifetime."))?
226 .clone();
227
228 let hdr = CommonHeader {
229 flavor: self.flavor,
230 lifetime,
231 client_versions: self.client_versions.clone(),
232 relay_versions: self.relay_versions.clone(),
233 client_protos: self.client_protos.clone(),
234 relay_protos: self.relay_protos.clone(),
235 params: self.params.clone(),
236 voting_delay: self.voting_delay,
237 };
238
239 let consensus_method = self
240 .consensus_method
241 .ok_or(Error::CannotBuild("Missing consensus method."))?;
242
243 let header = ConsensusHeader {
244 hdr,
245 consensus_method,
246 shared_rand_prev: self.shared_rand_prev.clone(),
247 shared_rand_cur: self.shared_rand_cur.clone(),
248 };
249
250 let footer = Footer {
251 weights: self.weights.clone(),
252 };
253
254 let mut relays = self.relays.clone();
255 relays.sort_by_key(|r| *r.rsa_identity());
256 Ok(Consensus {
259 header,
260 voters: self.voters.clone(),
261 relays,
262 footer,
263 })
264 }
265}
266
267pub struct VoterInfoBuilder {
269 nickname: Option<String>,
271 identity: Option<RsaIdentity>,
273 ip: Option<IpAddr>,
275 contact: Option<String>,
277 vote_digest: Vec<u8>,
279 or_port: u16,
281 dir_port: u16,
283}
284
285impl VoterInfoBuilder {
286 pub(crate) fn new() -> Self {
288 VoterInfoBuilder {
289 nickname: None,
290 identity: None,
291 ip: None,
292 contact: None,
293 vote_digest: Vec::new(),
294 or_port: 0,
295 dir_port: 0,
296 }
297 }
298
299 pub fn nickname(&mut self, nickname: String) -> &mut Self {
303 self.nickname = Some(nickname);
304 self
305 }
306
307 pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
311 self.identity = Some(identity);
312 self
313 }
314
315 pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
319 self.ip = Some(ip);
320 self
321 }
322
323 pub fn contact(&mut self, contact: String) -> &mut Self {
327 self.contact = Some(contact);
328 self
329 }
330
331 pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
335 self.vote_digest = vote_digest;
336 self
337 }
338
339 pub fn or_port(&mut self, or_port: u16) -> &mut Self {
341 self.or_port = or_port;
342 self
343 }
344
345 pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
347 self.dir_port = dir_port;
348 self
349 }
350
351 pub fn build<RS>(&self, builder: &mut ConsensusBuilder<RS>) -> Result<()> {
354 let nickname = self
355 .nickname
356 .as_ref()
357 .ok_or(Error::CannotBuild("Missing nickname"))?
358 .clone();
359 let identity = self
360 .identity
361 .ok_or(Error::CannotBuild("Missing identity"))?;
362 let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
363 let contact = self
364 .contact
365 .as_ref()
366 .ok_or(Error::CannotBuild("Missing contact"))?
367 .clone();
368 if self.vote_digest.is_empty() {
369 return Err(Error::CannotBuild("Missing vote digest"));
370 }
371 let dir_source = DirSource {
372 nickname,
373 identity,
374 ip,
375 dir_port: self.dir_port,
376 or_port: self.or_port,
377 };
378
379 let info = ConsensusVoterInfo {
380 dir_source,
381 contact,
382 vote_digest: self.vote_digest.clone(),
383 };
384 builder.voters.push(info);
385 Ok(())
386 }
387}
388
389#[cfg(test)]
390mod test {
391 #![allow(clippy::bool_assert_comparison)]
393 #![allow(clippy::clone_on_copy)]
394 #![allow(clippy::dbg_macro)]
395 #![allow(clippy::mixed_attributes_style)]
396 #![allow(clippy::print_stderr)]
397 #![allow(clippy::print_stdout)]
398 #![allow(clippy::single_char_pattern)]
399 #![allow(clippy::unwrap_used)]
400 #![allow(clippy::unchecked_duration_subtraction)]
401 #![allow(clippy::useless_vec)]
402 #![allow(clippy::needless_pass_by_value)]
403 use super::*;
405 use crate::doc::netstatus::RelayFlags;
406
407 use std::net::SocketAddr;
408 use std::time::{Duration, SystemTime};
409
410 #[test]
411 fn consensus() {
412 let now = SystemTime::now();
413 let one_hour = Duration::new(3600, 0);
414
415 let mut builder = crate::doc::netstatus::MdConsensus::builder();
416 builder
417 .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
418 .add_client_version("0.4.5.8".into())
419 .add_relay_version("0.4.5.9".into())
420 .required_client_protos("DirCache=2 LinkAuth=3".parse().unwrap())
421 .required_relay_protos("DirCache=1".parse().unwrap())
422 .recommended_client_protos("DirCache=6".parse().unwrap())
423 .recommended_relay_protos("DirCache=5".parse().unwrap())
424 .param("wombat", 7)
425 .param("knish", 1212)
426 .voting_delay(7, 8)
427 .consensus_method(32)
428 .shared_rand_prev(1, SharedRandVal([b'x'; 32]), None)
429 .shared_rand_cur(1, SharedRandVal([b'y'; 32]), None)
430 .weight("Wxy", 303)
431 .weight("Wow", 999);
432
433 builder
434 .voter()
435 .nickname("Fuzzy".into())
436 .identity([15; 20].into())
437 .ip("10.0.0.200".parse().unwrap())
438 .contact("admin@fuzzy.example.com".into())
439 .vote_digest((*b"1234").into())
440 .or_port(9001)
441 .dir_port(9101)
442 .build(&mut builder)
443 .unwrap();
444
445 builder
446 .rs()
447 .nickname("Fred".into())
448 .identity([155; 20].into())
449 .add_or_port(SocketAddr::from(([10, 0, 0, 60], 9100)))
450 .add_or_port("[f00f::1]:9200".parse().unwrap())
451 .doc_digest([99; 32])
452 .set_flags(RelayFlags::FAST)
453 .add_flags(RelayFlags::STABLE | RelayFlags::V2DIR)
454 .version("Arti 0.0.0".into())
455 .protos("DirCache=7".parse().unwrap())
456 .build_into(&mut builder)
457 .unwrap();
458
459 let _cons = builder.testing_consensus().unwrap();
460
461 }
463}