request.rs
1 //! Descriptions objects for different kinds of directory requests 2 //! that we can make. 3 4 use tor_llcrypto::pk::rsa::RsaIdentity; 5 use tor_netdoc::doc::authcert::AuthCertKeyIds; 6 use tor_netdoc::doc::microdesc::MdDigest; 7 use tor_netdoc::doc::netstatus::ConsensusFlavor; 8 #[cfg(feature = "routerdesc")] 9 use tor_netdoc::doc::routerdesc::RdDigest; 10 use tor_proto::circuit::ClientCirc; 11 12 #[cfg(feature = "hs-client")] 13 use tor_hscrypto::pk::HsBlindId; 14 15 /// Alias for a result with a `RequestError`. 16 type Result<T> = std::result::Result<T, crate::err::RequestError>; 17 18 use base64ct::{Base64Unpadded, Encoding as _}; 19 use std::borrow::Cow; 20 use std::iter::FromIterator; 21 use std::time::{Duration, SystemTime}; 22 23 use itertools::Itertools; 24 25 use crate::err::RequestError; 26 use crate::AnonymizedRequest; 27 28 /// Declare an inaccessible public type. 29 pub(crate) mod sealed { 30 use super::{AnonymizedRequest, ClientCirc, Result}; 31 /// Sealed trait to help implement [`Requestable`](super::Requestable): not 32 /// visible outside this crate, so we can change its methods however we like. 33 pub trait RequestableInner: Send + Sync { 34 /// Build an [`http::Request`] from this Requestable, if 35 /// it is well-formed. 36 // 37 // TODO: This API is a bit troublesome in how it takes &self and 38 // returns a Request<String>. First, most Requestables don't actually have 39 // a body to send, and for them having an empty String in their body is a 40 // bit silly. Second, taking a reference to self but returning an owned 41 // String means that we will often have to clone an internal string owned by 42 // this Requestable instance. 43 fn make_request(&self) -> Result<http::Request<String>>; 44 45 /// Return true if partial response bodies are potentially useful. 46 /// 47 /// This is true for request types where we're going to be downloading 48 /// multiple documents, and we know how to parse out the ones we wanted 49 /// if the answer is truncated. 50 fn partial_response_body_ok(&self) -> bool; 51 52 /// Return the maximum allowable response length we'll accept for this 53 /// request. 54 fn max_response_len(&self) -> usize { 55 (16 * 1024 * 1024) - 1 56 } 57 58 /// Return an error if there is some problem with the provided circuit that 59 /// would keep it from being used for this request. 60 fn check_circuit(&self, circ: &ClientCirc) -> Result<()> { 61 let _ = circ; 62 Ok(()) 63 } 64 65 /// Return a value to say whether this request must be anonymized. 66 fn anonymized(&self) -> AnonymizedRequest; 67 } 68 } 69 70 /// A request for an object that can be served over the Tor directory system. 71 pub trait Requestable: sealed::RequestableInner { 72 /// Return a wrapper around this [`Requestable`] that implements `Debug`, 73 /// and whose output shows the actual HTTP request that will be generated. 74 /// 75 /// The format is not guaranteed to be stable. 76 fn debug_request(&self) -> DisplayRequestable<'_, Self> 77 where 78 Self: Sized, 79 { 80 DisplayRequestable(self) 81 } 82 } 83 impl<T: sealed::RequestableInner> Requestable for T {} 84 85 /// A wrapper to implement [`Requestable::debug_request`]. 86 pub struct DisplayRequestable<'a, R: Requestable>(&'a R); 87 88 impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> { 89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 90 write!(f, "{:?}", self.0.make_request()) 91 } 92 } 93 94 /// How much clock skew do we allow in the distance between the directory 95 /// cache's clock and our own? 96 /// 97 /// If we find more skew than this, we end the 98 /// request early, on the theory that the directory will not tell us any 99 /// information we'd accept. 100 #[derive(Clone, Debug)] 101 struct SkewLimit { 102 /// We refuse to proceed if the directory says we are more fast than this. 103 /// 104 /// (This is equivalent to deciding that, from our perspective, the 105 /// directory is at least this slow.) 106 max_fast: Duration, 107 108 /// We refuse to proceed if the directory says that we are more slow than 109 /// this. 110 /// 111 /// (This is equivalent to deciding that, from our perspective, the 112 /// directory is at least this fast.) 113 max_slow: Duration, 114 } 115 116 /// A Request for a consensus directory. 117 #[derive(Debug, Clone)] 118 pub struct ConsensusRequest { 119 /// What flavor of consensus are we asking for? Right now, only 120 /// "microdesc" and "ns" are supported. 121 flavor: ConsensusFlavor, 122 /// A list of the authority identities that we believe in. We tell the 123 /// directory cache only to give us a consensus if it is signed by enough 124 /// of these authorities. 125 authority_ids: Vec<RsaIdentity>, 126 /// The publication time of the most recent consensus we have. Used to 127 /// generate an If-Modified-Since header so that we don't get a document 128 /// we already have. 129 last_consensus_published: Option<SystemTime>, 130 /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have. 131 /// Used to declare what diffs we would accept. 132 /// 133 /// (Currently we don't send this, since we can't handle diffs.) 134 last_consensus_sha3_256: Vec<[u8; 32]>, 135 /// If present, the largest amount of clock skew to allow between ourself and a directory cache. 136 skew_limit: Option<SkewLimit>, 137 } 138 139 impl ConsensusRequest { 140 /// Create a new request for a consensus directory document. 141 pub fn new(flavor: ConsensusFlavor) -> Self { 142 ConsensusRequest { 143 flavor, 144 authority_ids: Vec::new(), 145 last_consensus_published: None, 146 last_consensus_sha3_256: Vec::new(), 147 skew_limit: None, 148 } 149 } 150 151 /// Add `id` to the list of authorities that this request should 152 /// say we believe in. 153 pub fn push_authority_id(&mut self, id: RsaIdentity) { 154 self.authority_ids.push(id); 155 } 156 157 /// Add `d` to the list of consensus digests this request should 158 /// say we already have. 159 pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) { 160 self.last_consensus_sha3_256.push(d); 161 } 162 163 /// Set the publication time we should say we have for our last 164 /// consensus to `when`. 165 pub fn set_last_consensus_date(&mut self, when: SystemTime) { 166 self.last_consensus_published = Some(when); 167 } 168 169 /// Return a slice of the consensus digests that we're saying we 170 /// already have. 171 pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> { 172 self.last_consensus_sha3_256.iter() 173 } 174 175 /// Return an iterator of the authority identities that this request 176 /// is saying we believe in. 177 pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> { 178 self.authority_ids.iter() 179 } 180 181 /// Return the date we're reporting for our most recent consensus. 182 pub fn last_consensus_date(&self) -> Option<SystemTime> { 183 self.last_consensus_published 184 } 185 186 /// Tell the directory client that we should abort the request early if the 187 /// directory's clock skew exceeds certain limits. 188 /// 189 /// The `max_fast` parameter is the most fast that we're willing to be with 190 /// respect to the directory (or in other words, the most slow that we're 191 /// willing to let the directory be with respect to us). 192 /// 193 /// The `max_slow` parameter is the most _slow_ that we're willing to be with 194 /// respect to the directory ((or in other words, the most slow that we're 195 /// willing to let the directory be with respect to us). 196 pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) { 197 self.skew_limit = Some(SkewLimit { max_fast, max_slow }); 198 } 199 } 200 201 /// Convert a list of digests in some format to a string, for use in a request 202 /// 203 /// The digests `DL` will be sorted, converted to strings with `EF`, 204 /// separated with `sep`, and returned as an fresh `String`. 205 /// 206 /// If the digests list is empty, returns None instead. 207 // 208 // In principle this ought to be doable with much less allocating, 209 // starting with hex::encode etc. 210 fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String> 211 where 212 DL: IntoIterator<Item = &'d D> + 'd, 213 D: PartialOrd + Ord + 'd, 214 EF: Fn(&'d D) -> String, 215 { 216 let mut digests = digests.into_iter().collect_vec(); 217 if digests.is_empty() { 218 return None; 219 } 220 digests.sort_unstable(); 221 let ids = digests.into_iter().map(encode).map(Cow::Owned); 222 // name collision with unstable Iterator::intersperse 223 // https://github.com/rust-lang/rust/issues/48919 224 let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>(); 225 Some(ids) 226 } 227 228 impl Default for ConsensusRequest { 229 fn default() -> Self { 230 Self::new(ConsensusFlavor::Microdesc) 231 } 232 } 233 234 impl sealed::RequestableInner for ConsensusRequest { 235 fn make_request(&self) -> Result<http::Request<String>> { 236 // Build the URL. 237 let mut uri = "/tor/status-vote/current/consensus".to_string(); 238 match self.flavor { 239 ConsensusFlavor::Ns => {} 240 flav => { 241 uri.push('-'); 242 uri.push_str(flav.name()); 243 } 244 } 245 let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes()); 246 if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") { 247 // With authorities, "../consensus/<F1>+<F2>+<F3>.z" 248 uri.push('/'); 249 uri.push_str(&ids); 250 } 251 // Without authorities, "../consensus-microdesc.z" 252 uri.push_str(".z"); 253 254 let mut req = http::Request::builder().method("GET").uri(uri); 255 req = add_common_headers(req, self.anonymized()); 256 257 // Possibly, add an if-modified-since header. 258 if let Some(when) = self.last_consensus_date() { 259 req = req.header( 260 http::header::IF_MODIFIED_SINCE, 261 httpdate::fmt_http_date(when), 262 ); 263 } 264 265 // Possibly, add an X-Or-Diff-From-Consensus header. 266 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") { 267 req = req.header("X-Or-Diff-From-Consensus", &ids); 268 } 269 270 Ok(req.body(String::new())?) 271 } 272 273 fn partial_response_body_ok(&self) -> bool { 274 false 275 } 276 277 fn check_circuit(&self, circ: &ClientCirc) -> Result<()> { 278 use tor_proto::ClockSkew::*; 279 // This is the clock skew _according to the directory_. 280 let skew = circ.channel().clock_skew(); 281 match (&self.skew_limit, &skew) { 282 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => { 283 Err(RequestError::TooMuchClockSkew) 284 } 285 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => { 286 Err(RequestError::TooMuchClockSkew) 287 } 288 (_, _) => Ok(()), 289 } 290 } 291 292 fn anonymized(&self) -> AnonymizedRequest { 293 AnonymizedRequest::Direct 294 } 295 } 296 297 /// A request for one or more authority certificates. 298 #[derive(Debug, Clone, Default)] 299 pub struct AuthCertRequest { 300 /// The identity/signing keys of the certificates we want. 301 ids: Vec<AuthCertKeyIds>, 302 } 303 304 impl AuthCertRequest { 305 /// Create a new request, asking for no authority certificates. 306 pub fn new() -> Self { 307 AuthCertRequest::default() 308 } 309 310 /// Add `ids` to the list of certificates we're asking for. 311 pub fn push(&mut self, ids: AuthCertKeyIds) { 312 self.ids.push(ids); 313 } 314 315 /// Return a list of the keys that we're asking for. 316 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> { 317 self.ids.iter() 318 } 319 } 320 321 impl sealed::RequestableInner for AuthCertRequest { 322 fn make_request(&self) -> Result<http::Request<String>> { 323 if self.ids.is_empty() { 324 return Err(RequestError::EmptyRequest); 325 } 326 let mut ids = self.ids.clone(); 327 ids.sort_unstable(); 328 329 let ids: Vec<String> = ids 330 .iter() 331 .map(|id| { 332 format!( 333 "{}-{}", 334 hex::encode(id.id_fingerprint.as_bytes()), 335 hex::encode(id.sk_fingerprint.as_bytes()) 336 ) 337 }) 338 .collect(); 339 340 let uri = format!("/tor/keys/fp-sk/{}.z", &ids.join("+")); 341 342 let req = http::Request::builder().method("GET").uri(uri); 343 let req = add_common_headers(req, self.anonymized()); 344 345 Ok(req.body(String::new())?) 346 } 347 348 fn partial_response_body_ok(&self) -> bool { 349 self.ids.len() > 1 350 } 351 352 fn max_response_len(&self) -> usize { 353 // TODO: Pick a more principled number; I just made this one up. 354 self.ids.len().saturating_mul(16 * 1024) 355 } 356 357 fn anonymized(&self) -> AnonymizedRequest { 358 AnonymizedRequest::Direct 359 } 360 } 361 362 impl FromIterator<AuthCertKeyIds> for AuthCertRequest { 363 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self { 364 let mut req = Self::new(); 365 for i in iter { 366 req.push(i); 367 } 368 req 369 } 370 } 371 372 /// A request for one or more microdescriptors 373 #[derive(Debug, Clone, Default)] 374 pub struct MicrodescRequest { 375 /// The SHA256 digests of the microdescriptors we want. 376 digests: Vec<MdDigest>, 377 } 378 379 impl MicrodescRequest { 380 /// Construct a request for no microdescriptors. 381 pub fn new() -> Self { 382 MicrodescRequest::default() 383 } 384 /// Add `d` to the list of microdescriptors we want to request. 385 pub fn push(&mut self, d: MdDigest) { 386 self.digests.push(d); 387 } 388 389 /// Return a list of the microdescriptor digests that we're asking for. 390 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> { 391 self.digests.iter() 392 } 393 } 394 395 impl sealed::RequestableInner for MicrodescRequest { 396 fn make_request(&self) -> Result<http::Request<String>> { 397 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]); 398 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-") 399 .ok_or(RequestError::EmptyRequest)?; 400 let uri = format!("/tor/micro/d/{}.z", &ids); 401 let req = http::Request::builder().method("GET").uri(uri); 402 403 let req = add_common_headers(req, self.anonymized()); 404 405 Ok(req.body(String::new())?) 406 } 407 408 fn partial_response_body_ok(&self) -> bool { 409 self.digests.len() > 1 410 } 411 412 fn max_response_len(&self) -> usize { 413 // TODO: Pick a more principled number; I just made this one up. 414 self.digests.len().saturating_mul(8 * 1024) 415 } 416 417 fn anonymized(&self) -> AnonymizedRequest { 418 AnonymizedRequest::Direct 419 } 420 } 421 422 impl FromIterator<MdDigest> for MicrodescRequest { 423 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self { 424 let mut req = Self::new(); 425 for i in iter { 426 req.push(i); 427 } 428 req 429 } 430 } 431 432 /// A request for one, many or all router descriptors. 433 #[derive(Debug, Clone)] 434 #[cfg(feature = "routerdesc")] 435 pub struct RouterDescRequest { 436 /// The descriptors to request. 437 requested_descriptors: RequestedDescs, 438 } 439 440 /// Tracks the different router descriptor types. 441 #[derive(Debug, Clone)] 442 #[cfg(feature = "routerdesc")] 443 enum RequestedDescs { 444 /// If this is set, we just ask for all the descriptors. 445 AllDescriptors, 446 /// A list of digests to download. 447 Digests(Vec<RdDigest>), 448 } 449 450 #[cfg(feature = "routerdesc")] 451 impl Default for RouterDescRequest { 452 fn default() -> Self { 453 RouterDescRequest { 454 requested_descriptors: RequestedDescs::Digests(Vec::new()), 455 } 456 } 457 } 458 459 #[cfg(feature = "routerdesc")] 460 impl RouterDescRequest { 461 /// Construct a request for all router descriptors. 462 pub fn all() -> Self { 463 RouterDescRequest { 464 requested_descriptors: RequestedDescs::AllDescriptors, 465 } 466 } 467 /// Construct a new empty request. 468 pub fn new() -> Self { 469 RouterDescRequest::default() 470 } 471 } 472 473 #[cfg(feature = "routerdesc")] 474 impl sealed::RequestableInner for RouterDescRequest { 475 fn make_request(&self) -> Result<http::Request<String>> { 476 let mut uri = "/tor/server/".to_string(); 477 478 match self.requested_descriptors { 479 RequestedDescs::Digests(ref digests) => { 480 uri.push_str("d/"); 481 let ids = digest_list_stringify(digests, hex::encode, "+") 482 .ok_or(RequestError::EmptyRequest)?; 483 uri.push_str(&ids); 484 } 485 RequestedDescs::AllDescriptors => { 486 uri.push_str("all"); 487 } 488 } 489 490 uri.push_str(".z"); 491 492 let req = http::Request::builder().method("GET").uri(uri); 493 let req = add_common_headers(req, self.anonymized()); 494 495 Ok(req.body(String::new())?) 496 } 497 498 fn partial_response_body_ok(&self) -> bool { 499 match self.requested_descriptors { 500 RequestedDescs::Digests(ref digests) => digests.len() > 1, 501 RequestedDescs::AllDescriptors => true, 502 } 503 } 504 505 fn max_response_len(&self) -> usize { 506 // TODO: Pick a more principled number; I just made these up. 507 match self.requested_descriptors { 508 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024), 509 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible 510 } 511 } 512 513 fn anonymized(&self) -> AnonymizedRequest { 514 AnonymizedRequest::Direct 515 } 516 } 517 518 #[cfg(feature = "routerdesc")] 519 impl FromIterator<RdDigest> for RouterDescRequest { 520 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self { 521 let digests = iter.into_iter().collect(); 522 523 RouterDescRequest { 524 requested_descriptors: RequestedDescs::Digests(digests), 525 } 526 } 527 } 528 529 /// A request for the descriptor of whatever relay we are making the request to 530 #[derive(Debug, Clone, Default)] 531 #[cfg(feature = "routerdesc")] 532 #[non_exhaustive] 533 pub struct RoutersOwnDescRequest {} 534 535 #[cfg(feature = "routerdesc")] 536 impl RoutersOwnDescRequest { 537 /// Construct a new request. 538 pub fn new() -> Self { 539 RoutersOwnDescRequest::default() 540 } 541 } 542 543 #[cfg(feature = "routerdesc")] 544 impl sealed::RequestableInner for RoutersOwnDescRequest { 545 fn make_request(&self) -> Result<http::Request<String>> { 546 let uri = "/tor/server/authority.z"; 547 let req = http::Request::builder().method("GET").uri(uri); 548 let req = add_common_headers(req, self.anonymized()); 549 550 Ok(req.body(String::new())?) 551 } 552 553 fn partial_response_body_ok(&self) -> bool { 554 false 555 } 556 557 fn anonymized(&self) -> AnonymizedRequest { 558 AnonymizedRequest::Direct 559 } 560 } 561 562 /// A request to download a hidden service descriptor 563 /// 564 /// rend-spec-v3 2.2.6 565 #[derive(Debug, Clone)] 566 #[cfg(feature = "hs-client")] 567 pub struct HsDescDownloadRequest { 568 /// What hidden service? 569 hsid: HsBlindId, 570 /// What's the largest acceptable response length? 571 max_len: usize, 572 } 573 574 #[cfg(feature = "hs-client")] 575 impl HsDescDownloadRequest { 576 /// Construct a request for a single onion service descriptor by its 577 /// blinded ID. 578 pub fn new(hsid: HsBlindId) -> Self { 579 /// Default maximum length to use when we have no other information. 580 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000; 581 HsDescDownloadRequest { 582 hsid, 583 max_len: DEFAULT_HSDESC_MAX_LEN, 584 } 585 } 586 587 /// Set the maximum acceptable response length. 588 pub fn set_max_len(&mut self, max_len: usize) { 589 self.max_len = max_len; 590 } 591 } 592 593 #[cfg(feature = "hs-client")] 594 impl sealed::RequestableInner for HsDescDownloadRequest { 595 fn make_request(&self) -> Result<http::Request<String>> { 596 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref()); 597 // We hardcode version 3 here; if we ever have a v4 onion service 598 // descriptor, it will need a different kind of Request. 599 let uri = format!("/tor/hs/3/{}", hsid); 600 let req = http::Request::builder().method("GET").uri(uri); 601 let req = add_common_headers(req, self.anonymized()); 602 Ok(req.body(String::new())?) 603 } 604 605 fn partial_response_body_ok(&self) -> bool { 606 false 607 } 608 609 fn max_response_len(&self) -> usize { 610 self.max_len 611 } 612 613 fn anonymized(&self) -> AnonymizedRequest { 614 AnonymizedRequest::Anonymized 615 } 616 } 617 618 /// A request to upload a hidden service descriptor 619 /// 620 /// rend-spec-v3 2.2.6 621 #[derive(Debug, Clone)] 622 #[cfg(feature = "hs-service")] 623 pub struct HsDescUploadRequest(String); 624 625 #[cfg(feature = "hs-service")] 626 impl HsDescUploadRequest { 627 /// Construct a request for uploading a single onion service descriptor. 628 pub fn new(hsdesc: String) -> Self { 629 HsDescUploadRequest(hsdesc) 630 } 631 } 632 633 #[cfg(feature = "hs-service")] 634 impl sealed::RequestableInner for HsDescUploadRequest { 635 fn make_request(&self) -> Result<http::Request<String>> { 636 /// The upload URI. 637 const URI: &str = "/tor/hs/3/publish"; 638 639 let req = http::Request::builder().method("POST").uri(URI); 640 let req = add_common_headers(req, self.anonymized()); 641 Ok(req.body(self.0.clone())?) 642 } 643 644 fn partial_response_body_ok(&self) -> bool { 645 false 646 } 647 648 fn max_response_len(&self) -> usize { 649 // We expect the response _body_ to be empty, but the max_response_len 650 // is not zero because it represents the _total_ length of the response 651 // (which includes the length of the status line and headers). 652 // 653 // A real Tor POST response will always be less than that length, which 654 // will fit into 3 DATA messages at most. (The reply will be a single 655 // HTTP line, followed by a Date header.) 656 1024 657 } 658 659 fn anonymized(&self) -> AnonymizedRequest { 660 AnonymizedRequest::Anonymized 661 } 662 } 663 664 /// Encodings that all Tor clients support. 665 const UNIVERSAL_ENCODINGS: &str = "deflate, identity"; 666 667 /// List all the encodings we accept 668 fn all_encodings() -> String { 669 #[allow(unused_mut)] 670 let mut encodings = UNIVERSAL_ENCODINGS.to_string(); 671 #[cfg(feature = "xz")] 672 { 673 encodings += ", x-tor-lzma"; 674 } 675 #[cfg(feature = "zstd")] 676 { 677 encodings += ", x-zstd"; 678 } 679 680 encodings 681 } 682 683 /// Add commonly used headers to the HTTP request. 684 /// 685 /// (Right now, this is only Accept-Encoding.) 686 fn add_common_headers( 687 req: http::request::Builder, 688 anon: AnonymizedRequest, 689 ) -> http::request::Builder { 690 // TODO: gzip, brotli 691 match anon { 692 AnonymizedRequest::Anonymized => { 693 // In an anonymized request, we do not admit to supporting any 694 // encoding besides those that are always available. 695 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS) 696 } 697 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()), 698 } 699 } 700 701 #[cfg(test)] 702 mod test { 703 // @@ begin test lint list maintained by maint/add_warning @@ 704 #![allow(clippy::bool_assert_comparison)] 705 #![allow(clippy::clone_on_copy)] 706 #![allow(clippy::dbg_macro)] 707 #![allow(clippy::mixed_attributes_style)] 708 #![allow(clippy::print_stderr)] 709 #![allow(clippy::print_stdout)] 710 #![allow(clippy::single_char_pattern)] 711 #![allow(clippy::unwrap_used)] 712 #![allow(clippy::unchecked_duration_subtraction)] 713 #![allow(clippy::useless_vec)] 714 #![allow(clippy::needless_pass_by_value)] 715 //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> 716 use super::sealed::RequestableInner; 717 use super::*; 718 719 #[test] 720 fn test_md_request() -> Result<()> { 721 let d1 = b"This is a testing digest. it isn"; 722 let d2 = b"'t actually SHA-256............."; 723 724 let mut req = MicrodescRequest::default(); 725 req.push(*d1); 726 assert!(!req.partial_response_body_ok()); 727 req.push(*d2); 728 assert!(req.partial_response_body_ok()); 729 assert_eq!(req.max_response_len(), 16 << 10); 730 731 let req = crate::util::encode_request(&req.make_request()?); 732 733 assert_eq!(req, 734 format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings())); 735 736 // Try it with FromIterator, and use some accessors. 737 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect(); 738 let ds: Vec<_> = req2.digests().collect(); 739 assert_eq!(ds, vec![d1, d2]); 740 let req2 = crate::util::encode_request(&req2.make_request()?); 741 assert_eq!(req, req2); 742 743 Ok(()) 744 } 745 746 #[test] 747 fn test_cert_request() -> Result<()> { 748 let d1 = b"This is a testing dn"; 749 let d2 = b"'t actually SHA-256."; 750 let key1 = AuthCertKeyIds { 751 id_fingerprint: (*d1).into(), 752 sk_fingerprint: (*d2).into(), 753 }; 754 755 let d3 = b"blah blah blah 1 2 3"; 756 let d4 = b"I like pizza from Na"; 757 let key2 = AuthCertKeyIds { 758 id_fingerprint: (*d3).into(), 759 sk_fingerprint: (*d4).into(), 760 }; 761 762 let mut req = AuthCertRequest::default(); 763 req.push(key1); 764 assert!(!req.partial_response_body_ok()); 765 req.push(key2); 766 assert!(req.partial_response_body_ok()); 767 assert_eq!(req.max_response_len(), 32 << 10); 768 769 let keys: Vec<_> = req.keys().collect(); 770 assert_eq!(keys, vec![&key1, &key2]); 771 772 let req = crate::util::encode_request(&req.make_request()?); 773 774 assert_eq!(req, 775 format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings())); 776 777 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect(); 778 let req2 = crate::util::encode_request(&req2.make_request()?); 779 assert_eq!(req, req2); 780 781 Ok(()) 782 } 783 784 #[test] 785 fn test_consensus_request() -> Result<()> { 786 let d1 = RsaIdentity::from_bytes( 787 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(), 788 ) 789 .unwrap(); 790 791 let d2 = b"blah blah blah 12 blah blah blah"; 792 let d3 = SystemTime::now(); 793 let mut req = ConsensusRequest::default(); 794 795 let when = httpdate::fmt_http_date(d3); 796 797 req.push_authority_id(d1); 798 req.push_old_consensus_digest(*d2); 799 req.set_last_consensus_date(d3); 800 assert!(!req.partial_response_body_ok()); 801 assert_eq!(req.max_response_len(), (16 << 20) - 1); 802 assert_eq!(req.old_consensus_digests().next(), Some(d2)); 803 assert_eq!(req.authority_ids().next(), Some(&d1)); 804 assert_eq!(req.last_consensus_date(), Some(d3)); 805 806 let req = crate::util::encode_request(&req.make_request()?); 807 808 assert_eq!(req, 809 format!("GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e.z HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n", all_encodings(), when)); 810 811 // Request without authorities 812 let req = ConsensusRequest::default(); 813 let req = crate::util::encode_request(&req.make_request()?); 814 assert_eq!(req, 815 format!("GET /tor/status-vote/current/consensus-microdesc.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings())); 816 817 Ok(()) 818 } 819 820 #[test] 821 #[cfg(feature = "routerdesc")] 822 fn test_rd_request_all() -> Result<()> { 823 let req = RouterDescRequest::all(); 824 assert!(req.partial_response_body_ok()); 825 assert_eq!(req.max_response_len(), 1 << 26); 826 827 let req = crate::util::encode_request(&req.make_request()?); 828 829 assert_eq!( 830 req, 831 format!( 832 "GET /tor/server/all.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", 833 all_encodings() 834 ) 835 ); 836 837 Ok(()) 838 } 839 840 #[test] 841 #[cfg(feature = "routerdesc")] 842 fn test_rd_request() -> Result<()> { 843 let d1 = b"at some point I got "; 844 let d2 = b"of writing in hex..."; 845 846 let mut req = RouterDescRequest::default(); 847 848 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors { 849 digests.push(*d1); 850 } 851 assert!(!req.partial_response_body_ok()); 852 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors { 853 digests.push(*d2); 854 } 855 assert!(req.partial_response_body_ok()); 856 assert_eq!(req.max_response_len(), 16 << 10); 857 858 let req = crate::util::encode_request(&req.make_request()?); 859 860 assert_eq!(req, 861 format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings())); 862 863 // Try it with FromIterator, and use some accessors. 864 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect(); 865 let ds: Vec<_> = match req2.requested_descriptors { 866 RequestedDescs::Digests(ref digests) => digests.iter().collect(), 867 RequestedDescs::AllDescriptors => Vec::new(), 868 }; 869 assert_eq!(ds, vec![d1, d2]); 870 let req2 = crate::util::encode_request(&req2.make_request()?); 871 assert_eq!(req, req2); 872 Ok(()) 873 } 874 875 #[test] 876 #[cfg(feature = "hs-client")] 877 fn test_hs_desc_download_request() -> Result<()> { 878 use tor_llcrypto::pk::ed25519::Ed25519Identity; 879 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec(); 880 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap()); 881 let hsid = HsBlindId::from(hsid); 882 let req = HsDescDownloadRequest::new(hsid); 883 assert!(!req.partial_response_body_ok()); 884 assert_eq!(req.max_response_len(), 50 * 1000); 885 886 let req = crate::util::encode_request(&req.make_request()?); 887 888 assert_eq!( 889 req, 890 format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS) 891 ); 892 893 Ok(()) 894 } 895 }