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