1use tor_circmgr::ClientDirTunnel;
5use tor_llcrypto::pk::rsa::RsaIdentity;
6use tor_netdoc::doc::authcert::AuthCertKeyIds;
7use tor_netdoc::doc::microdesc::MdDigest;
8use tor_netdoc::doc::netstatus::ConsensusFlavor;
9#[cfg(feature = "routerdesc")]
10use tor_netdoc::doc::routerdesc::{ExtraInfoDigest, RdDigest};
11
12#[cfg(feature = "hs-client")]
13use tor_hscrypto::pk::HsBlindId;
14
15type Result<T> = std::result::Result<T, crate::err::RequestError>;
17
18use base64ct::{Base64Unpadded, Encoding as _};
19use std::borrow::Cow;
20use std::future::Future;
21use std::iter::FromIterator;
22use std::pin::Pin;
23use std::time::{Duration, SystemTime};
24
25use itertools::Itertools;
26
27use crate::AnonymizedRequest;
28use crate::err::RequestError;
29
30pub(crate) mod sealed {
32 use tor_circmgr::ClientDirTunnel;
33
34 use super::{AnonymizedRequest, Result};
35
36 use std::future::Future;
37 use std::pin::Pin;
38
39 pub trait RequestableInner: Send + Sync {
42 fn make_request(&self) -> Result<http::Request<String>>;
52
53 fn partial_response_body_ok(&self) -> bool;
59
60 fn max_response_len(&self) -> usize {
63 (16 * 1024 * 1024) - 1
64 }
65
66 fn check_circuit<'a>(
69 &self,
70 tunnel: &'a ClientDirTunnel,
71 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
72 let _ = tunnel;
73 Box::pin(async { Ok(()) })
74 }
75
76 fn anonymized(&self) -> AnonymizedRequest;
78 }
79}
80
81pub trait Requestable: sealed::RequestableInner {
83 fn debug_request(&self) -> DisplayRequestable<'_, Self>
88 where
89 Self: Sized,
90 {
91 DisplayRequestable(self)
92 }
93}
94impl<T: sealed::RequestableInner> Requestable for T {}
95
96pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
98
99impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{:?}", self.0.make_request())
102 }
103}
104
105#[derive(Clone, Debug)]
112struct SkewLimit {
113 max_fast: Duration,
118
119 max_slow: Duration,
125}
126
127#[derive(Debug, Clone)]
129pub struct ConsensusRequest {
130 flavor: ConsensusFlavor,
133 authority_ids: Vec<RsaIdentity>,
137 last_consensus_published: Option<SystemTime>,
141 last_consensus_sha3_256: Vec<[u8; 32]>,
144 skew_limit: Option<SkewLimit>,
146}
147
148impl ConsensusRequest {
149 pub fn new(flavor: ConsensusFlavor) -> Self {
151 ConsensusRequest {
152 flavor,
153 authority_ids: Vec::new(),
154 last_consensus_published: None,
155 last_consensus_sha3_256: Vec::new(),
156 skew_limit: None,
157 }
158 }
159
160 pub fn push_authority_id(&mut self, id: RsaIdentity) {
163 self.authority_ids.push(id);
164 }
165
166 pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
169 self.last_consensus_sha3_256.push(d);
170 }
171
172 pub fn set_last_consensus_date(&mut self, when: SystemTime) {
175 self.last_consensus_published = Some(when);
176 }
177
178 pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
181 self.last_consensus_sha3_256.iter()
182 }
183
184 pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
187 self.authority_ids.iter()
188 }
189
190 pub fn last_consensus_date(&self) -> Option<SystemTime> {
192 self.last_consensus_published
193 }
194
195 pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
206 self.skew_limit = Some(SkewLimit { max_fast, max_slow });
207 }
208}
209
210fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
220where
221 DL: IntoIterator<Item = &'d D> + 'd,
222 D: PartialOrd + Ord + 'd,
223 EF: Fn(&'d D) -> String,
224{
225 let mut digests = digests.into_iter().collect_vec();
226 if digests.is_empty() {
227 return None;
228 }
229 digests.sort_unstable();
230 let ids = digests.into_iter().map(encode).map(Cow::Owned);
231 let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
234 Some(ids)
235}
236
237impl Default for ConsensusRequest {
238 fn default() -> Self {
239 Self::new(ConsensusFlavor::Microdesc)
240 }
241}
242
243impl sealed::RequestableInner for ConsensusRequest {
244 fn make_request(&self) -> Result<http::Request<String>> {
245 let mut uri = "/tor/status-vote/current/consensus".to_string();
247 match self.flavor {
248 ConsensusFlavor::Plain => {}
249 flav => {
250 uri.push('-');
251 uri.push_str(flav.name());
252 }
253 }
254 let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
255 if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
256 uri.push('/');
258 uri.push_str(&ids);
259 }
260 let mut req = http::Request::builder().method("GET").uri(uri);
263 req = add_common_headers(req, self.anonymized());
264
265 if let Some(when) = self.last_consensus_date() {
267 req = req.header(
268 http::header::IF_MODIFIED_SINCE,
269 httpdate::fmt_http_date(when),
270 );
271 }
272
273 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
275 req = req.header("X-Or-Diff-From-Consensus", &ids);
276 }
277
278 Ok(req.body(String::new())?)
279 }
280
281 fn partial_response_body_ok(&self) -> bool {
282 false
283 }
284
285 fn check_circuit<'a>(
286 &self,
287 tunnel: &'a ClientDirTunnel,
288 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
289 let skew_limit = self.skew_limit.clone();
290 Box::pin(async move {
291 use tor_proto::ClockSkew::*;
292 let skew = tunnel.first_hop_clock_skew().await?;
294 match (&skew_limit, &skew) {
295 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
296 Err(RequestError::TooMuchClockSkew)
297 }
298 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
299 Err(RequestError::TooMuchClockSkew)
300 }
301 (_, _) => Ok(()),
302 }
303 })
304 }
305
306 fn anonymized(&self) -> AnonymizedRequest {
307 AnonymizedRequest::Direct
308 }
309}
310
311#[derive(Debug, Clone, Default)]
313pub struct AuthCertRequest {
314 ids: Vec<AuthCertKeyIds>,
316}
317
318impl AuthCertRequest {
319 pub fn new() -> Self {
321 AuthCertRequest::default()
322 }
323
324 pub fn push(&mut self, ids: AuthCertKeyIds) {
326 self.ids.push(ids);
327 }
328
329 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
331 self.ids.iter()
332 }
333}
334
335impl sealed::RequestableInner for AuthCertRequest {
336 fn make_request(&self) -> Result<http::Request<String>> {
337 if self.ids.is_empty() {
338 return Err(RequestError::EmptyRequest);
339 }
340 let mut ids = self.ids.clone();
341 ids.sort_unstable();
342
343 let ids: Vec<String> = ids
344 .iter()
345 .map(|id| {
346 format!(
347 "{}-{}",
348 hex::encode(id.id_fingerprint.as_bytes()),
349 hex::encode(id.sk_fingerprint.as_bytes())
350 )
351 })
352 .collect();
353
354 let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
355
356 let req = http::Request::builder().method("GET").uri(uri);
357 let req = add_common_headers(req, self.anonymized());
358
359 Ok(req.body(String::new())?)
360 }
361
362 fn partial_response_body_ok(&self) -> bool {
363 self.ids.len() > 1
364 }
365
366 fn max_response_len(&self) -> usize {
367 self.ids.len().saturating_mul(16 * 1024)
369 }
370
371 fn anonymized(&self) -> AnonymizedRequest {
372 AnonymizedRequest::Direct
373 }
374}
375
376impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
377 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
378 let mut req = Self::new();
379 for i in iter {
380 req.push(i);
381 }
382 req
383 }
384}
385
386#[derive(Debug, Clone, Default)]
388pub struct MicrodescRequest {
389 digests: Vec<MdDigest>,
391}
392
393impl MicrodescRequest {
394 pub fn new() -> Self {
396 MicrodescRequest::default()
397 }
398 pub fn push(&mut self, d: MdDigest) {
400 self.digests.push(d);
401 }
402
403 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
405 self.digests.iter()
406 }
407}
408
409impl sealed::RequestableInner for MicrodescRequest {
410 fn make_request(&self) -> Result<http::Request<String>> {
411 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
412 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
413 .ok_or(RequestError::EmptyRequest)?;
414 let uri = format!("/tor/micro/d/{}", &ids);
415 let req = http::Request::builder().method("GET").uri(uri);
416
417 let req = add_common_headers(req, self.anonymized());
418
419 Ok(req.body(String::new())?)
420 }
421
422 fn partial_response_body_ok(&self) -> bool {
423 self.digests.len() > 1
424 }
425
426 fn max_response_len(&self) -> usize {
427 self.digests.len().saturating_mul(8 * 1024)
429 }
430
431 fn anonymized(&self) -> AnonymizedRequest {
432 AnonymizedRequest::Direct
433 }
434}
435
436impl FromIterator<MdDigest> for MicrodescRequest {
437 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
438 let mut req = Self::new();
439 for i in iter {
440 req.push(i);
441 }
442 req
443 }
444}
445
446#[derive(Debug, Clone)]
448#[cfg(feature = "routerdesc")]
449pub struct RouterDescRequest {
450 requested_descriptors: RequestedDescs,
452}
453
454#[derive(Debug, Clone)]
456#[cfg(feature = "routerdesc")]
457enum RequestedDescs {
458 AllDescriptors,
460 Digests(Vec<RdDigest>),
462}
463
464#[cfg(feature = "routerdesc")]
465impl Default for RouterDescRequest {
467 fn default() -> Self {
468 RouterDescRequest {
469 requested_descriptors: RequestedDescs::Digests(Vec::new()),
470 }
471 }
472}
473
474#[cfg(feature = "routerdesc")]
475impl RouterDescRequest {
476 pub fn all() -> Self {
478 RouterDescRequest {
479 requested_descriptors: RequestedDescs::AllDescriptors,
480 }
481 }
482 pub fn new() -> Self {
484 RouterDescRequest::default()
485 }
486}
487
488#[cfg(feature = "routerdesc")]
489impl sealed::RequestableInner for RouterDescRequest {
490 fn make_request(&self) -> Result<http::Request<String>> {
491 let mut uri = "/tor/server/".to_string();
492
493 match self.requested_descriptors {
494 RequestedDescs::Digests(ref digests) => {
495 uri.push_str("d/");
496 let ids = digest_list_stringify(digests, hex::encode, "+")
497 .ok_or(RequestError::EmptyRequest)?;
498 uri.push_str(&ids);
499 }
500 RequestedDescs::AllDescriptors => {
501 uri.push_str("all");
502 }
503 }
504
505 let req = http::Request::builder().method("GET").uri(uri);
506 let req = add_common_headers(req, self.anonymized());
507
508 Ok(req.body(String::new())?)
509 }
510
511 fn partial_response_body_ok(&self) -> bool {
512 match self.requested_descriptors {
513 RequestedDescs::Digests(ref digests) => digests.len() > 1,
514 RequestedDescs::AllDescriptors => true,
515 }
516 }
517
518 fn max_response_len(&self) -> usize {
519 match self.requested_descriptors {
521 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
522 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, }
524 }
525
526 fn anonymized(&self) -> AnonymizedRequest {
527 AnonymizedRequest::Direct
528 }
529}
530
531#[cfg(feature = "routerdesc")]
532impl FromIterator<RdDigest> for RouterDescRequest {
533 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
534 let digests = iter.into_iter().collect();
535
536 RouterDescRequest {
537 requested_descriptors: RequestedDescs::Digests(digests),
538 }
539 }
540}
541
542#[derive(Debug, Clone, Default)]
544#[cfg(feature = "routerdesc")]
545#[non_exhaustive]
546pub struct RoutersOwnDescRequest {}
547
548#[cfg(feature = "routerdesc")]
549impl RoutersOwnDescRequest {
550 pub fn new() -> Self {
552 RoutersOwnDescRequest::default()
553 }
554}
555
556#[cfg(feature = "routerdesc")]
557impl sealed::RequestableInner for RoutersOwnDescRequest {
558 fn make_request(&self) -> Result<http::Request<String>> {
559 let uri = "/tor/server/authority";
560 let req = http::Request::builder().method("GET").uri(uri);
561 let req = add_common_headers(req, self.anonymized());
562
563 Ok(req.body(String::new())?)
564 }
565
566 fn partial_response_body_ok(&self) -> bool {
567 false
568 }
569
570 fn anonymized(&self) -> AnonymizedRequest {
571 AnonymizedRequest::Direct
572 }
573}
574
575#[derive(Debug, Clone)]
580#[cfg(feature = "routerdesc")]
581pub struct ExtraInfoRequest {
582 requested_extra_infos: RequestedExtraInfos,
584}
585
586#[derive(Debug, Clone)]
590#[cfg(feature = "routerdesc")]
591#[non_exhaustive]
592enum RequestedExtraInfos {
593 AllExtraInfos,
598 Digests(Vec<ExtraInfoDigest>),
602}
603
604#[cfg(feature = "routerdesc")]
605impl Default for ExtraInfoRequest {
606 fn default() -> Self {
608 Self {
609 requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
610 }
611 }
612}
613
614#[cfg(feature = "routerdesc")]
615impl ExtraInfoRequest {
616 pub fn all() -> Self {
618 Self {
619 requested_extra_infos: RequestedExtraInfos::AllExtraInfos,
620 }
621 }
622 pub fn new() -> Self {
624 Self::default()
625 }
626}
627
628#[cfg(feature = "routerdesc")]
629impl sealed::RequestableInner for ExtraInfoRequest {
630 fn make_request(&self) -> Result<http::Request<String>> {
631 let mut uri = "/tor/extra/".to_string();
632
633 match &self.requested_extra_infos {
634 RequestedExtraInfos::AllExtraInfos => uri.push_str("all"),
635 RequestedExtraInfos::Digests(digests) => {
636 uri.push_str("d/");
637 let ids = digest_list_stringify(digests, hex::encode_upper, "+")
638 .ok_or(RequestError::EmptyRequest)?;
639 uri.push_str(&ids);
640 }
641 }
642
643 let req = http::Request::builder().method("GET").uri(uri);
644 let req = add_common_headers(req, self.anonymized());
645 Ok(req.body(String::new())?)
646 }
647
648 fn partial_response_body_ok(&self) -> bool {
649 match &self.requested_extra_infos {
650 RequestedExtraInfos::Digests(digests) => digests.len() > 1,
651 RequestedExtraInfos::AllExtraInfos => true,
652 }
653 }
654
655 fn max_response_len(&self) -> usize {
656 match &self.requested_extra_infos {
659 RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
660 RequestedExtraInfos::AllExtraInfos => 128 * 1024 * 1024,
661 }
662 }
663
664 fn anonymized(&self) -> AnonymizedRequest {
665 AnonymizedRequest::Direct
666 }
667}
668
669#[cfg(feature = "routerdesc")]
670impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
671 fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
672 Self {
673 requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
674 }
675 }
676}
677
678#[derive(Debug, Clone)]
682#[cfg(feature = "hs-client")]
683pub struct HsDescDownloadRequest {
684 hsid: HsBlindId,
686 max_len: usize,
688}
689
690#[cfg(feature = "hs-client")]
691impl HsDescDownloadRequest {
692 pub fn new(hsid: HsBlindId) -> Self {
695 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
697 HsDescDownloadRequest {
698 hsid,
699 max_len: DEFAULT_HSDESC_MAX_LEN,
700 }
701 }
702
703 pub fn set_max_len(&mut self, max_len: usize) {
705 self.max_len = max_len;
706 }
707}
708
709#[cfg(feature = "hs-client")]
710impl sealed::RequestableInner for HsDescDownloadRequest {
711 fn make_request(&self) -> Result<http::Request<String>> {
712 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
713 let uri = format!("/tor/hs/3/{}", hsid);
716 let req = http::Request::builder().method("GET").uri(uri);
717 let req = add_common_headers(req, self.anonymized());
718 Ok(req.body(String::new())?)
719 }
720
721 fn partial_response_body_ok(&self) -> bool {
722 false
723 }
724
725 fn max_response_len(&self) -> usize {
726 self.max_len
727 }
728
729 fn anonymized(&self) -> AnonymizedRequest {
730 AnonymizedRequest::Anonymized
731 }
732}
733
734#[derive(Debug, Clone)]
738#[cfg(feature = "hs-service")]
739pub struct HsDescUploadRequest(String);
740
741#[cfg(feature = "hs-service")]
742impl HsDescUploadRequest {
743 pub fn new(hsdesc: String) -> Self {
745 HsDescUploadRequest(hsdesc)
746 }
747}
748
749#[cfg(feature = "hs-service")]
750impl sealed::RequestableInner for HsDescUploadRequest {
751 fn make_request(&self) -> Result<http::Request<String>> {
752 const URI: &str = "/tor/hs/3/publish";
754
755 let req = http::Request::builder().method("POST").uri(URI);
756 let req = add_common_headers(req, self.anonymized());
757 Ok(req.body(self.0.clone())?)
758 }
759
760 fn partial_response_body_ok(&self) -> bool {
761 false
762 }
763
764 fn max_response_len(&self) -> usize {
765 1024
773 }
774
775 fn anonymized(&self) -> AnonymizedRequest {
776 AnonymizedRequest::Anonymized
777 }
778}
779
780const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
782
783fn all_encodings() -> String {
785 #[allow(unused_mut)]
786 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
787 #[cfg(feature = "xz")]
788 {
789 encodings += ", x-tor-lzma";
790 }
791 #[cfg(feature = "zstd")]
792 {
793 encodings += ", x-zstd";
794 }
795
796 encodings
797}
798
799fn add_common_headers(
803 req: http::request::Builder,
804 anon: AnonymizedRequest,
805) -> http::request::Builder {
806 match anon {
808 AnonymizedRequest::Anonymized => {
809 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
812 }
813 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
814 }
815}
816
817#[cfg(test)]
818mod test {
819 #![allow(clippy::bool_assert_comparison)]
821 #![allow(clippy::clone_on_copy)]
822 #![allow(clippy::dbg_macro)]
823 #![allow(clippy::mixed_attributes_style)]
824 #![allow(clippy::print_stderr)]
825 #![allow(clippy::print_stdout)]
826 #![allow(clippy::single_char_pattern)]
827 #![allow(clippy::unwrap_used)]
828 #![allow(clippy::unchecked_time_subtraction)]
829 #![allow(clippy::useless_vec)]
830 #![allow(clippy::needless_pass_by_value)]
831 use super::sealed::RequestableInner;
833 use super::*;
834 use web_time_compat::SystemTimeExt;
835
836 #[test]
837 fn test_md_request() -> Result<()> {
838 let d1 = b"This is a testing digest. it isn";
839 let d2 = b"'t actually SHA-256.............";
840
841 let mut req = MicrodescRequest::default();
842 req.push(*d1);
843 assert!(!req.partial_response_body_ok());
844 req.push(*d2);
845 assert!(req.partial_response_body_ok());
846 assert_eq!(req.max_response_len(), 16 << 10);
847
848 let req = crate::util::encode_request(&req.make_request()?);
849
850 assert_eq!(
851 req,
852 format!(
853 "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
854 all_encodings()
855 )
856 );
857
858 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
860 let ds: Vec<_> = req2.digests().collect();
861 assert_eq!(ds, vec![d1, d2]);
862 let req2 = crate::util::encode_request(&req2.make_request()?);
863 assert_eq!(req, req2);
864
865 Ok(())
866 }
867
868 #[test]
869 fn test_cert_request() -> Result<()> {
870 let d1 = b"This is a testing dn";
871 let d2 = b"'t actually SHA-256.";
872 let key1 = AuthCertKeyIds {
873 id_fingerprint: (*d1).into(),
874 sk_fingerprint: (*d2).into(),
875 };
876
877 let d3 = b"blah blah blah 1 2 3";
878 let d4 = b"I like pizza from Na";
879 let key2 = AuthCertKeyIds {
880 id_fingerprint: (*d3).into(),
881 sk_fingerprint: (*d4).into(),
882 };
883
884 let mut req = AuthCertRequest::default();
885 req.push(key1);
886 assert!(!req.partial_response_body_ok());
887 req.push(key2);
888 assert!(req.partial_response_body_ok());
889 assert_eq!(req.max_response_len(), 32 << 10);
890
891 let keys: Vec<_> = req.keys().collect();
892 assert_eq!(keys, vec![&key1, &key2]);
893
894 let req = crate::util::encode_request(&req.make_request()?);
895
896 assert_eq!(
897 req,
898 format!(
899 "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
900 all_encodings()
901 )
902 );
903
904 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
905 let req2 = crate::util::encode_request(&req2.make_request()?);
906 assert_eq!(req, req2);
907
908 Ok(())
909 }
910
911 #[test]
912 fn test_consensus_request() -> Result<()> {
913 let d1 = RsaIdentity::from_bytes(
914 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
915 )
916 .unwrap();
917
918 let d2 = b"blah blah blah 12 blah blah blah";
919 let d3 = SystemTime::get();
920 let mut req = ConsensusRequest::default();
921
922 let when = httpdate::fmt_http_date(d3);
923
924 req.push_authority_id(d1);
925 req.push_old_consensus_digest(*d2);
926 req.set_last_consensus_date(d3);
927 assert!(!req.partial_response_body_ok());
928 assert_eq!(req.max_response_len(), (16 << 20) - 1);
929 assert_eq!(req.old_consensus_digests().next(), Some(d2));
930 assert_eq!(req.authority_ids().next(), Some(&d1));
931 assert_eq!(req.last_consensus_date(), Some(d3));
932
933 let req = crate::util::encode_request(&req.make_request()?);
934
935 assert_eq!(
936 req,
937 format!(
938 "GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n",
939 all_encodings(),
940 when
941 )
942 );
943
944 let req = ConsensusRequest::default();
946 let req = crate::util::encode_request(&req.make_request()?);
947 assert_eq!(
948 req,
949 format!(
950 "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
951 all_encodings()
952 )
953 );
954
955 Ok(())
956 }
957
958 #[test]
959 #[cfg(feature = "routerdesc")]
960 fn test_rd_request_all() -> Result<()> {
961 let req = RouterDescRequest::all();
962 assert!(req.partial_response_body_ok());
963 assert_eq!(req.max_response_len(), 1 << 26);
964
965 let req = crate::util::encode_request(&req.make_request()?);
966
967 assert_eq!(
968 req,
969 format!(
970 "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
971 all_encodings()
972 )
973 );
974
975 Ok(())
976 }
977
978 #[test]
979 #[cfg(feature = "routerdesc")]
980 fn test_rd_request() -> Result<()> {
981 let d1 = b"at some point I got ";
982 let d2 = b"of writing in hex...";
983
984 let mut req = RouterDescRequest::default();
985
986 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
987 digests.push(*d1);
988 }
989 assert!(!req.partial_response_body_ok());
990 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
991 digests.push(*d2);
992 }
993 assert!(req.partial_response_body_ok());
994 assert_eq!(req.max_response_len(), 16 << 10);
995
996 let req = crate::util::encode_request(&req.make_request()?);
997
998 assert_eq!(
999 req,
1000 format!(
1001 "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1002 all_encodings()
1003 )
1004 );
1005
1006 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
1008 let ds: Vec<_> = match req2.requested_descriptors {
1009 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
1010 RequestedDescs::AllDescriptors => Vec::new(),
1011 };
1012 assert_eq!(ds, vec![d1, d2]);
1013 let req2 = crate::util::encode_request(&req2.make_request()?);
1014 assert_eq!(req, req2);
1015 Ok(())
1016 }
1017
1018 #[test]
1019 #[cfg(feature = "routerdesc")]
1020 fn test_extra_info_request() -> Result<()> {
1021 let req = ExtraInfoRequest::from_iter([[0; 20], [1; 20], [2; 20]]);
1022 assert_eq!(
1023 crate::util::encode_request(&req.make_request()?),
1024 format!(
1025 "GET /tor/extra/d/{}+{}+{} HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1026 hex::encode_upper([0; 20]),
1027 hex::encode_upper([1; 20]),
1028 hex::encode_upper([2; 20]),
1029 all_encodings()
1030 )
1031 );
1032
1033 let req = ExtraInfoRequest::all();
1034 assert_eq!(
1035 crate::util::encode_request(&req.make_request()?),
1036 format!(
1037 "GET /tor/extra/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1038 all_encodings()
1039 )
1040 );
1041 Ok(())
1042 }
1043
1044 #[test]
1045 #[cfg(feature = "hs-client")]
1046 fn test_hs_desc_download_request() -> Result<()> {
1047 use tor_llcrypto::pk::ed25519::Ed25519Identity;
1048 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
1049 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
1050 let hsid = HsBlindId::from(hsid);
1051 let req = HsDescDownloadRequest::new(hsid);
1052 assert!(!req.partial_response_body_ok());
1053 assert_eq!(req.max_response_len(), 50 * 1000);
1054
1055 let req = crate::util::encode_request(&req.make_request()?);
1056
1057 assert_eq!(
1058 req,
1059 format!(
1060 "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1061 UNIVERSAL_ENCODINGS
1062 )
1063 );
1064
1065 Ok(())
1066 }
1067}