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::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::Ns => {}
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 {
466 fn default() -> Self {
467 RouterDescRequest {
468 requested_descriptors: RequestedDescs::Digests(Vec::new()),
469 }
470 }
471}
472
473#[cfg(feature = "routerdesc")]
474impl RouterDescRequest {
475 pub fn all() -> Self {
477 RouterDescRequest {
478 requested_descriptors: RequestedDescs::AllDescriptors,
479 }
480 }
481 pub fn new() -> Self {
483 RouterDescRequest::default()
484 }
485}
486
487#[cfg(feature = "routerdesc")]
488impl sealed::RequestableInner for RouterDescRequest {
489 fn make_request(&self) -> Result<http::Request<String>> {
490 let mut uri = "/tor/server/".to_string();
491
492 match self.requested_descriptors {
493 RequestedDescs::Digests(ref digests) => {
494 uri.push_str("d/");
495 let ids = digest_list_stringify(digests, hex::encode, "+")
496 .ok_or(RequestError::EmptyRequest)?;
497 uri.push_str(&ids);
498 }
499 RequestedDescs::AllDescriptors => {
500 uri.push_str("all");
501 }
502 }
503
504 let req = http::Request::builder().method("GET").uri(uri);
505 let req = add_common_headers(req, self.anonymized());
506
507 Ok(req.body(String::new())?)
508 }
509
510 fn partial_response_body_ok(&self) -> bool {
511 match self.requested_descriptors {
512 RequestedDescs::Digests(ref digests) => digests.len() > 1,
513 RequestedDescs::AllDescriptors => true,
514 }
515 }
516
517 fn max_response_len(&self) -> usize {
518 match self.requested_descriptors {
520 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
521 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, }
523 }
524
525 fn anonymized(&self) -> AnonymizedRequest {
526 AnonymizedRequest::Direct
527 }
528}
529
530#[cfg(feature = "routerdesc")]
531impl FromIterator<RdDigest> for RouterDescRequest {
532 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
533 let digests = iter.into_iter().collect();
534
535 RouterDescRequest {
536 requested_descriptors: RequestedDescs::Digests(digests),
537 }
538 }
539}
540
541#[derive(Debug, Clone, Default)]
543#[cfg(feature = "routerdesc")]
544#[non_exhaustive]
545pub struct RoutersOwnDescRequest {}
546
547#[cfg(feature = "routerdesc")]
548impl RoutersOwnDescRequest {
549 pub fn new() -> Self {
551 RoutersOwnDescRequest::default()
552 }
553}
554
555#[cfg(feature = "routerdesc")]
556impl sealed::RequestableInner for RoutersOwnDescRequest {
557 fn make_request(&self) -> Result<http::Request<String>> {
558 let uri = "/tor/server/authority";
559 let req = http::Request::builder().method("GET").uri(uri);
560 let req = add_common_headers(req, self.anonymized());
561
562 Ok(req.body(String::new())?)
563 }
564
565 fn partial_response_body_ok(&self) -> bool {
566 false
567 }
568
569 fn anonymized(&self) -> AnonymizedRequest {
570 AnonymizedRequest::Direct
571 }
572}
573
574#[derive(Debug, Clone)]
578#[cfg(feature = "hs-client")]
579pub struct HsDescDownloadRequest {
580 hsid: HsBlindId,
582 max_len: usize,
584}
585
586#[cfg(feature = "hs-client")]
587impl HsDescDownloadRequest {
588 pub fn new(hsid: HsBlindId) -> Self {
591 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
593 HsDescDownloadRequest {
594 hsid,
595 max_len: DEFAULT_HSDESC_MAX_LEN,
596 }
597 }
598
599 pub fn set_max_len(&mut self, max_len: usize) {
601 self.max_len = max_len;
602 }
603}
604
605#[cfg(feature = "hs-client")]
606impl sealed::RequestableInner for HsDescDownloadRequest {
607 fn make_request(&self) -> Result<http::Request<String>> {
608 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
609 let uri = format!("/tor/hs/3/{}", hsid);
612 let req = http::Request::builder().method("GET").uri(uri);
613 let req = add_common_headers(req, self.anonymized());
614 Ok(req.body(String::new())?)
615 }
616
617 fn partial_response_body_ok(&self) -> bool {
618 false
619 }
620
621 fn max_response_len(&self) -> usize {
622 self.max_len
623 }
624
625 fn anonymized(&self) -> AnonymizedRequest {
626 AnonymizedRequest::Anonymized
627 }
628}
629
630#[derive(Debug, Clone)]
634#[cfg(feature = "hs-service")]
635pub struct HsDescUploadRequest(String);
636
637#[cfg(feature = "hs-service")]
638impl HsDescUploadRequest {
639 pub fn new(hsdesc: String) -> Self {
641 HsDescUploadRequest(hsdesc)
642 }
643}
644
645#[cfg(feature = "hs-service")]
646impl sealed::RequestableInner for HsDescUploadRequest {
647 fn make_request(&self) -> Result<http::Request<String>> {
648 const URI: &str = "/tor/hs/3/publish";
650
651 let req = http::Request::builder().method("POST").uri(URI);
652 let req = add_common_headers(req, self.anonymized());
653 Ok(req.body(self.0.clone())?)
654 }
655
656 fn partial_response_body_ok(&self) -> bool {
657 false
658 }
659
660 fn max_response_len(&self) -> usize {
661 1024
669 }
670
671 fn anonymized(&self) -> AnonymizedRequest {
672 AnonymizedRequest::Anonymized
673 }
674}
675
676const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
678
679fn all_encodings() -> String {
681 #[allow(unused_mut)]
682 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
683 #[cfg(feature = "xz")]
684 {
685 encodings += ", x-tor-lzma";
686 }
687 #[cfg(feature = "zstd")]
688 {
689 encodings += ", x-zstd";
690 }
691
692 encodings
693}
694
695fn add_common_headers(
699 req: http::request::Builder,
700 anon: AnonymizedRequest,
701) -> http::request::Builder {
702 match anon {
704 AnonymizedRequest::Anonymized => {
705 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
708 }
709 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
710 }
711}
712
713#[cfg(test)]
714mod test {
715 #![allow(clippy::bool_assert_comparison)]
717 #![allow(clippy::clone_on_copy)]
718 #![allow(clippy::dbg_macro)]
719 #![allow(clippy::mixed_attributes_style)]
720 #![allow(clippy::print_stderr)]
721 #![allow(clippy::print_stdout)]
722 #![allow(clippy::single_char_pattern)]
723 #![allow(clippy::unwrap_used)]
724 #![allow(clippy::unchecked_duration_subtraction)]
725 #![allow(clippy::useless_vec)]
726 #![allow(clippy::needless_pass_by_value)]
727 use super::sealed::RequestableInner;
729 use super::*;
730
731 #[test]
732 fn test_md_request() -> Result<()> {
733 let d1 = b"This is a testing digest. it isn";
734 let d2 = b"'t actually SHA-256.............";
735
736 let mut req = MicrodescRequest::default();
737 req.push(*d1);
738 assert!(!req.partial_response_body_ok());
739 req.push(*d2);
740 assert!(req.partial_response_body_ok());
741 assert_eq!(req.max_response_len(), 16 << 10);
742
743 let req = crate::util::encode_request(&req.make_request()?);
744
745 assert_eq!(
746 req,
747 format!(
748 "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
749 all_encodings()
750 )
751 );
752
753 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
755 let ds: Vec<_> = req2.digests().collect();
756 assert_eq!(ds, vec![d1, d2]);
757 let req2 = crate::util::encode_request(&req2.make_request()?);
758 assert_eq!(req, req2);
759
760 Ok(())
761 }
762
763 #[test]
764 fn test_cert_request() -> Result<()> {
765 let d1 = b"This is a testing dn";
766 let d2 = b"'t actually SHA-256.";
767 let key1 = AuthCertKeyIds {
768 id_fingerprint: (*d1).into(),
769 sk_fingerprint: (*d2).into(),
770 };
771
772 let d3 = b"blah blah blah 1 2 3";
773 let d4 = b"I like pizza from Na";
774 let key2 = AuthCertKeyIds {
775 id_fingerprint: (*d3).into(),
776 sk_fingerprint: (*d4).into(),
777 };
778
779 let mut req = AuthCertRequest::default();
780 req.push(key1);
781 assert!(!req.partial_response_body_ok());
782 req.push(key2);
783 assert!(req.partial_response_body_ok());
784 assert_eq!(req.max_response_len(), 32 << 10);
785
786 let keys: Vec<_> = req.keys().collect();
787 assert_eq!(keys, vec![&key1, &key2]);
788
789 let req = crate::util::encode_request(&req.make_request()?);
790
791 assert_eq!(
792 req,
793 format!(
794 "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
795 all_encodings()
796 )
797 );
798
799 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
800 let req2 = crate::util::encode_request(&req2.make_request()?);
801 assert_eq!(req, req2);
802
803 Ok(())
804 }
805
806 #[test]
807 fn test_consensus_request() -> Result<()> {
808 let d1 = RsaIdentity::from_bytes(
809 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
810 )
811 .unwrap();
812
813 let d2 = b"blah blah blah 12 blah blah blah";
814 let d3 = SystemTime::now();
815 let mut req = ConsensusRequest::default();
816
817 let when = httpdate::fmt_http_date(d3);
818
819 req.push_authority_id(d1);
820 req.push_old_consensus_digest(*d2);
821 req.set_last_consensus_date(d3);
822 assert!(!req.partial_response_body_ok());
823 assert_eq!(req.max_response_len(), (16 << 20) - 1);
824 assert_eq!(req.old_consensus_digests().next(), Some(d2));
825 assert_eq!(req.authority_ids().next(), Some(&d1));
826 assert_eq!(req.last_consensus_date(), Some(d3));
827
828 let req = crate::util::encode_request(&req.make_request()?);
829
830 assert_eq!(
831 req,
832 format!(
833 "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",
834 all_encodings(),
835 when
836 )
837 );
838
839 let req = ConsensusRequest::default();
841 let req = crate::util::encode_request(&req.make_request()?);
842 assert_eq!(
843 req,
844 format!(
845 "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
846 all_encodings()
847 )
848 );
849
850 Ok(())
851 }
852
853 #[test]
854 #[cfg(feature = "routerdesc")]
855 fn test_rd_request_all() -> Result<()> {
856 let req = RouterDescRequest::all();
857 assert!(req.partial_response_body_ok());
858 assert_eq!(req.max_response_len(), 1 << 26);
859
860 let req = crate::util::encode_request(&req.make_request()?);
861
862 assert_eq!(
863 req,
864 format!(
865 "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
866 all_encodings()
867 )
868 );
869
870 Ok(())
871 }
872
873 #[test]
874 #[cfg(feature = "routerdesc")]
875 fn test_rd_request() -> Result<()> {
876 let d1 = b"at some point I got ";
877 let d2 = b"of writing in hex...";
878
879 let mut req = RouterDescRequest::default();
880
881 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
882 digests.push(*d1);
883 }
884 assert!(!req.partial_response_body_ok());
885 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
886 digests.push(*d2);
887 }
888 assert!(req.partial_response_body_ok());
889 assert_eq!(req.max_response_len(), 16 << 10);
890
891 let req = crate::util::encode_request(&req.make_request()?);
892
893 assert_eq!(
894 req,
895 format!(
896 "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
897 all_encodings()
898 )
899 );
900
901 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
903 let ds: Vec<_> = match req2.requested_descriptors {
904 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
905 RequestedDescs::AllDescriptors => Vec::new(),
906 };
907 assert_eq!(ds, vec![d1, d2]);
908 let req2 = crate::util::encode_request(&req2.make_request()?);
909 assert_eq!(req, req2);
910 Ok(())
911 }
912
913 #[test]
914 #[cfg(feature = "hs-client")]
915 fn test_hs_desc_download_request() -> Result<()> {
916 use tor_llcrypto::pk::ed25519::Ed25519Identity;
917 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
918 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
919 let hsid = HsBlindId::from(hsid);
920 let req = HsDescDownloadRequest::new(hsid);
921 assert!(!req.partial_response_body_ok());
922 assert_eq!(req.max_response_len(), 50 * 1000);
923
924 let req = crate::util::encode_request(&req.make_request()?);
925
926 assert_eq!(
927 req,
928 format!(
929 "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
930 UNIVERSAL_ENCODINGS
931 )
932 );
933
934 Ok(())
935 }
936}