1use tor_llcrypto::pk::rsa::RsaIdentity;
5use tor_netdoc::doc::authcert::AuthCertKeyIds;
6use tor_netdoc::doc::microdesc::MdDigest;
7use tor_netdoc::doc::netstatus::ConsensusFlavor;
8#[cfg(feature = "routerdesc")]
9use tor_netdoc::doc::routerdesc::RdDigest;
10use tor_proto::circuit::ClientCirc;
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::err::RequestError;
28use crate::AnonymizedRequest;
29
30pub(crate) mod sealed {
32 use super::{AnonymizedRequest, ClientCirc, Result};
33
34 use std::future::Future;
35 use std::pin::Pin;
36
37 pub trait RequestableInner: Send + Sync {
40 fn make_request(&self) -> Result<http::Request<String>>;
50
51 fn partial_response_body_ok(&self) -> bool;
57
58 fn max_response_len(&self) -> usize {
61 (16 * 1024 * 1024) - 1
62 }
63
64 fn check_circuit<'a>(
67 &self,
68 circ: &'a ClientCirc,
69 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
70 let _ = circ;
71 Box::pin(async { Ok(()) })
72 }
73
74 fn anonymized(&self) -> AnonymizedRequest;
76 }
77}
78
79pub trait Requestable: sealed::RequestableInner {
81 fn debug_request(&self) -> DisplayRequestable<'_, Self>
86 where
87 Self: Sized,
88 {
89 DisplayRequestable(self)
90 }
91}
92impl<T: sealed::RequestableInner> Requestable for T {}
93
94pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
96
97impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 write!(f, "{:?}", self.0.make_request())
100 }
101}
102
103#[derive(Clone, Debug)]
110struct SkewLimit {
111 max_fast: Duration,
116
117 max_slow: Duration,
123}
124
125#[derive(Debug, Clone)]
127pub struct ConsensusRequest {
128 flavor: ConsensusFlavor,
131 authority_ids: Vec<RsaIdentity>,
135 last_consensus_published: Option<SystemTime>,
139 last_consensus_sha3_256: Vec<[u8; 32]>,
142 skew_limit: Option<SkewLimit>,
144}
145
146impl ConsensusRequest {
147 pub fn new(flavor: ConsensusFlavor) -> Self {
149 ConsensusRequest {
150 flavor,
151 authority_ids: Vec::new(),
152 last_consensus_published: None,
153 last_consensus_sha3_256: Vec::new(),
154 skew_limit: None,
155 }
156 }
157
158 pub fn push_authority_id(&mut self, id: RsaIdentity) {
161 self.authority_ids.push(id);
162 }
163
164 pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
167 self.last_consensus_sha3_256.push(d);
168 }
169
170 pub fn set_last_consensus_date(&mut self, when: SystemTime) {
173 self.last_consensus_published = Some(when);
174 }
175
176 pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
179 self.last_consensus_sha3_256.iter()
180 }
181
182 pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
185 self.authority_ids.iter()
186 }
187
188 pub fn last_consensus_date(&self) -> Option<SystemTime> {
190 self.last_consensus_published
191 }
192
193 pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
204 self.skew_limit = Some(SkewLimit { max_fast, max_slow });
205 }
206}
207
208fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
218where
219 DL: IntoIterator<Item = &'d D> + 'd,
220 D: PartialOrd + Ord + 'd,
221 EF: Fn(&'d D) -> String,
222{
223 let mut digests = digests.into_iter().collect_vec();
224 if digests.is_empty() {
225 return None;
226 }
227 digests.sort_unstable();
228 let ids = digests.into_iter().map(encode).map(Cow::Owned);
229 let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
232 Some(ids)
233}
234
235impl Default for ConsensusRequest {
236 fn default() -> Self {
237 Self::new(ConsensusFlavor::Microdesc)
238 }
239}
240
241impl sealed::RequestableInner for ConsensusRequest {
242 fn make_request(&self) -> Result<http::Request<String>> {
243 let mut uri = "/tor/status-vote/current/consensus".to_string();
245 match self.flavor {
246 ConsensusFlavor::Ns => {}
247 flav => {
248 uri.push('-');
249 uri.push_str(flav.name());
250 }
251 }
252 let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
253 if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
254 uri.push('/');
256 uri.push_str(&ids);
257 }
258 let mut req = http::Request::builder().method("GET").uri(uri);
261 req = add_common_headers(req, self.anonymized());
262
263 if let Some(when) = self.last_consensus_date() {
265 req = req.header(
266 http::header::IF_MODIFIED_SINCE,
267 httpdate::fmt_http_date(when),
268 );
269 }
270
271 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
273 req = req.header("X-Or-Diff-From-Consensus", &ids);
274 }
275
276 Ok(req.body(String::new())?)
277 }
278
279 fn partial_response_body_ok(&self) -> bool {
280 false
281 }
282
283 fn check_circuit<'a>(
284 &self,
285 circ: &'a ClientCirc,
286 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
287 let skew_limit = self.skew_limit.clone();
288 Box::pin(async move {
289 use tor_proto::ClockSkew::*;
290 let skew = circ.first_hop_clock_skew().await?;
292 match (&skew_limit, &skew) {
293 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
294 Err(RequestError::TooMuchClockSkew)
295 }
296 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
297 Err(RequestError::TooMuchClockSkew)
298 }
299 (_, _) => Ok(()),
300 }
301 })
302 }
303
304 fn anonymized(&self) -> AnonymizedRequest {
305 AnonymizedRequest::Direct
306 }
307}
308
309#[derive(Debug, Clone, Default)]
311pub struct AuthCertRequest {
312 ids: Vec<AuthCertKeyIds>,
314}
315
316impl AuthCertRequest {
317 pub fn new() -> Self {
319 AuthCertRequest::default()
320 }
321
322 pub fn push(&mut self, ids: AuthCertKeyIds) {
324 self.ids.push(ids);
325 }
326
327 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
329 self.ids.iter()
330 }
331}
332
333impl sealed::RequestableInner for AuthCertRequest {
334 fn make_request(&self) -> Result<http::Request<String>> {
335 if self.ids.is_empty() {
336 return Err(RequestError::EmptyRequest);
337 }
338 let mut ids = self.ids.clone();
339 ids.sort_unstable();
340
341 let ids: Vec<String> = ids
342 .iter()
343 .map(|id| {
344 format!(
345 "{}-{}",
346 hex::encode(id.id_fingerprint.as_bytes()),
347 hex::encode(id.sk_fingerprint.as_bytes())
348 )
349 })
350 .collect();
351
352 let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
353
354 let req = http::Request::builder().method("GET").uri(uri);
355 let req = add_common_headers(req, self.anonymized());
356
357 Ok(req.body(String::new())?)
358 }
359
360 fn partial_response_body_ok(&self) -> bool {
361 self.ids.len() > 1
362 }
363
364 fn max_response_len(&self) -> usize {
365 self.ids.len().saturating_mul(16 * 1024)
367 }
368
369 fn anonymized(&self) -> AnonymizedRequest {
370 AnonymizedRequest::Direct
371 }
372}
373
374impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
375 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
376 let mut req = Self::new();
377 for i in iter {
378 req.push(i);
379 }
380 req
381 }
382}
383
384#[derive(Debug, Clone, Default)]
386pub struct MicrodescRequest {
387 digests: Vec<MdDigest>,
389}
390
391impl MicrodescRequest {
392 pub fn new() -> Self {
394 MicrodescRequest::default()
395 }
396 pub fn push(&mut self, d: MdDigest) {
398 self.digests.push(d);
399 }
400
401 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
403 self.digests.iter()
404 }
405}
406
407impl sealed::RequestableInner for MicrodescRequest {
408 fn make_request(&self) -> Result<http::Request<String>> {
409 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
410 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
411 .ok_or(RequestError::EmptyRequest)?;
412 let uri = format!("/tor/micro/d/{}", &ids);
413 let req = http::Request::builder().method("GET").uri(uri);
414
415 let req = add_common_headers(req, self.anonymized());
416
417 Ok(req.body(String::new())?)
418 }
419
420 fn partial_response_body_ok(&self) -> bool {
421 self.digests.len() > 1
422 }
423
424 fn max_response_len(&self) -> usize {
425 self.digests.len().saturating_mul(8 * 1024)
427 }
428
429 fn anonymized(&self) -> AnonymizedRequest {
430 AnonymizedRequest::Direct
431 }
432}
433
434impl FromIterator<MdDigest> for MicrodescRequest {
435 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
436 let mut req = Self::new();
437 for i in iter {
438 req.push(i);
439 }
440 req
441 }
442}
443
444#[derive(Debug, Clone)]
446#[cfg(feature = "routerdesc")]
447pub struct RouterDescRequest {
448 requested_descriptors: RequestedDescs,
450}
451
452#[derive(Debug, Clone)]
454#[cfg(feature = "routerdesc")]
455enum RequestedDescs {
456 AllDescriptors,
458 Digests(Vec<RdDigest>),
460}
461
462#[cfg(feature = "routerdesc")]
463impl Default for RouterDescRequest {
464 fn default() -> Self {
465 RouterDescRequest {
466 requested_descriptors: RequestedDescs::Digests(Vec::new()),
467 }
468 }
469}
470
471#[cfg(feature = "routerdesc")]
472impl RouterDescRequest {
473 pub fn all() -> Self {
475 RouterDescRequest {
476 requested_descriptors: RequestedDescs::AllDescriptors,
477 }
478 }
479 pub fn new() -> Self {
481 RouterDescRequest::default()
482 }
483}
484
485#[cfg(feature = "routerdesc")]
486impl sealed::RequestableInner for RouterDescRequest {
487 fn make_request(&self) -> Result<http::Request<String>> {
488 let mut uri = "/tor/server/".to_string();
489
490 match self.requested_descriptors {
491 RequestedDescs::Digests(ref digests) => {
492 uri.push_str("d/");
493 let ids = digest_list_stringify(digests, hex::encode, "+")
494 .ok_or(RequestError::EmptyRequest)?;
495 uri.push_str(&ids);
496 }
497 RequestedDescs::AllDescriptors => {
498 uri.push_str("all");
499 }
500 }
501
502 let req = http::Request::builder().method("GET").uri(uri);
503 let req = add_common_headers(req, self.anonymized());
504
505 Ok(req.body(String::new())?)
506 }
507
508 fn partial_response_body_ok(&self) -> bool {
509 match self.requested_descriptors {
510 RequestedDescs::Digests(ref digests) => digests.len() > 1,
511 RequestedDescs::AllDescriptors => true,
512 }
513 }
514
515 fn max_response_len(&self) -> usize {
516 match self.requested_descriptors {
518 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
519 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, }
521 }
522
523 fn anonymized(&self) -> AnonymizedRequest {
524 AnonymizedRequest::Direct
525 }
526}
527
528#[cfg(feature = "routerdesc")]
529impl FromIterator<RdDigest> for RouterDescRequest {
530 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
531 let digests = iter.into_iter().collect();
532
533 RouterDescRequest {
534 requested_descriptors: RequestedDescs::Digests(digests),
535 }
536 }
537}
538
539#[derive(Debug, Clone, Default)]
541#[cfg(feature = "routerdesc")]
542#[non_exhaustive]
543pub struct RoutersOwnDescRequest {}
544
545#[cfg(feature = "routerdesc")]
546impl RoutersOwnDescRequest {
547 pub fn new() -> Self {
549 RoutersOwnDescRequest::default()
550 }
551}
552
553#[cfg(feature = "routerdesc")]
554impl sealed::RequestableInner for RoutersOwnDescRequest {
555 fn make_request(&self) -> Result<http::Request<String>> {
556 let uri = "/tor/server/authority";
557 let req = http::Request::builder().method("GET").uri(uri);
558 let req = add_common_headers(req, self.anonymized());
559
560 Ok(req.body(String::new())?)
561 }
562
563 fn partial_response_body_ok(&self) -> bool {
564 false
565 }
566
567 fn anonymized(&self) -> AnonymizedRequest {
568 AnonymizedRequest::Direct
569 }
570}
571
572#[derive(Debug, Clone)]
576#[cfg(feature = "hs-client")]
577pub struct HsDescDownloadRequest {
578 hsid: HsBlindId,
580 max_len: usize,
582}
583
584#[cfg(feature = "hs-client")]
585impl HsDescDownloadRequest {
586 pub fn new(hsid: HsBlindId) -> Self {
589 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
591 HsDescDownloadRequest {
592 hsid,
593 max_len: DEFAULT_HSDESC_MAX_LEN,
594 }
595 }
596
597 pub fn set_max_len(&mut self, max_len: usize) {
599 self.max_len = max_len;
600 }
601}
602
603#[cfg(feature = "hs-client")]
604impl sealed::RequestableInner for HsDescDownloadRequest {
605 fn make_request(&self) -> Result<http::Request<String>> {
606 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
607 let uri = format!("/tor/hs/3/{}", hsid);
610 let req = http::Request::builder().method("GET").uri(uri);
611 let req = add_common_headers(req, self.anonymized());
612 Ok(req.body(String::new())?)
613 }
614
615 fn partial_response_body_ok(&self) -> bool {
616 false
617 }
618
619 fn max_response_len(&self) -> usize {
620 self.max_len
621 }
622
623 fn anonymized(&self) -> AnonymizedRequest {
624 AnonymizedRequest::Anonymized
625 }
626}
627
628#[derive(Debug, Clone)]
632#[cfg(feature = "hs-service")]
633pub struct HsDescUploadRequest(String);
634
635#[cfg(feature = "hs-service")]
636impl HsDescUploadRequest {
637 pub fn new(hsdesc: String) -> Self {
639 HsDescUploadRequest(hsdesc)
640 }
641}
642
643#[cfg(feature = "hs-service")]
644impl sealed::RequestableInner for HsDescUploadRequest {
645 fn make_request(&self) -> Result<http::Request<String>> {
646 const URI: &str = "/tor/hs/3/publish";
648
649 let req = http::Request::builder().method("POST").uri(URI);
650 let req = add_common_headers(req, self.anonymized());
651 Ok(req.body(self.0.clone())?)
652 }
653
654 fn partial_response_body_ok(&self) -> bool {
655 false
656 }
657
658 fn max_response_len(&self) -> usize {
659 1024
667 }
668
669 fn anonymized(&self) -> AnonymizedRequest {
670 AnonymizedRequest::Anonymized
671 }
672}
673
674const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
676
677fn all_encodings() -> String {
679 #[allow(unused_mut)]
680 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
681 #[cfg(feature = "xz")]
682 {
683 encodings += ", x-tor-lzma";
684 }
685 #[cfg(feature = "zstd")]
686 {
687 encodings += ", x-zstd";
688 }
689
690 encodings
691}
692
693fn add_common_headers(
697 req: http::request::Builder,
698 anon: AnonymizedRequest,
699) -> http::request::Builder {
700 match anon {
702 AnonymizedRequest::Anonymized => {
703 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
706 }
707 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
708 }
709}
710
711#[cfg(test)]
712mod test {
713 #![allow(clippy::bool_assert_comparison)]
715 #![allow(clippy::clone_on_copy)]
716 #![allow(clippy::dbg_macro)]
717 #![allow(clippy::mixed_attributes_style)]
718 #![allow(clippy::print_stderr)]
719 #![allow(clippy::print_stdout)]
720 #![allow(clippy::single_char_pattern)]
721 #![allow(clippy::unwrap_used)]
722 #![allow(clippy::unchecked_duration_subtraction)]
723 #![allow(clippy::useless_vec)]
724 #![allow(clippy::needless_pass_by_value)]
725 use super::sealed::RequestableInner;
727 use super::*;
728
729 #[test]
730 fn test_md_request() -> Result<()> {
731 let d1 = b"This is a testing digest. it isn";
732 let d2 = b"'t actually SHA-256.............";
733
734 let mut req = MicrodescRequest::default();
735 req.push(*d1);
736 assert!(!req.partial_response_body_ok());
737 req.push(*d2);
738 assert!(req.partial_response_body_ok());
739 assert_eq!(req.max_response_len(), 16 << 10);
740
741 let req = crate::util::encode_request(&req.make_request()?);
742
743 assert_eq!(req,
744 format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
745
746 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
748 let ds: Vec<_> = req2.digests().collect();
749 assert_eq!(ds, vec![d1, d2]);
750 let req2 = crate::util::encode_request(&req2.make_request()?);
751 assert_eq!(req, req2);
752
753 Ok(())
754 }
755
756 #[test]
757 fn test_cert_request() -> Result<()> {
758 let d1 = b"This is a testing dn";
759 let d2 = b"'t actually SHA-256.";
760 let key1 = AuthCertKeyIds {
761 id_fingerprint: (*d1).into(),
762 sk_fingerprint: (*d2).into(),
763 };
764
765 let d3 = b"blah blah blah 1 2 3";
766 let d4 = b"I like pizza from Na";
767 let key2 = AuthCertKeyIds {
768 id_fingerprint: (*d3).into(),
769 sk_fingerprint: (*d4).into(),
770 };
771
772 let mut req = AuthCertRequest::default();
773 req.push(key1);
774 assert!(!req.partial_response_body_ok());
775 req.push(key2);
776 assert!(req.partial_response_body_ok());
777 assert_eq!(req.max_response_len(), 32 << 10);
778
779 let keys: Vec<_> = req.keys().collect();
780 assert_eq!(keys, vec![&key1, &key2]);
781
782 let req = crate::util::encode_request(&req.make_request()?);
783
784 assert_eq!(req,
785 format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
786
787 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
788 let req2 = crate::util::encode_request(&req2.make_request()?);
789 assert_eq!(req, req2);
790
791 Ok(())
792 }
793
794 #[test]
795 fn test_consensus_request() -> Result<()> {
796 let d1 = RsaIdentity::from_bytes(
797 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
798 )
799 .unwrap();
800
801 let d2 = b"blah blah blah 12 blah blah blah";
802 let d3 = SystemTime::now();
803 let mut req = ConsensusRequest::default();
804
805 let when = httpdate::fmt_http_date(d3);
806
807 req.push_authority_id(d1);
808 req.push_old_consensus_digest(*d2);
809 req.set_last_consensus_date(d3);
810 assert!(!req.partial_response_body_ok());
811 assert_eq!(req.max_response_len(), (16 << 20) - 1);
812 assert_eq!(req.old_consensus_digests().next(), Some(d2));
813 assert_eq!(req.authority_ids().next(), Some(&d1));
814 assert_eq!(req.last_consensus_date(), Some(d3));
815
816 let req = crate::util::encode_request(&req.make_request()?);
817
818 assert_eq!(req,
819 format!("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", all_encodings(), when));
820
821 let req = ConsensusRequest::default();
823 let req = crate::util::encode_request(&req.make_request()?);
824 assert_eq!(req,
825 format!("GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
826
827 Ok(())
828 }
829
830 #[test]
831 #[cfg(feature = "routerdesc")]
832 fn test_rd_request_all() -> Result<()> {
833 let req = RouterDescRequest::all();
834 assert!(req.partial_response_body_ok());
835 assert_eq!(req.max_response_len(), 1 << 26);
836
837 let req = crate::util::encode_request(&req.make_request()?);
838
839 assert_eq!(
840 req,
841 format!(
842 "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
843 all_encodings()
844 )
845 );
846
847 Ok(())
848 }
849
850 #[test]
851 #[cfg(feature = "routerdesc")]
852 fn test_rd_request() -> Result<()> {
853 let d1 = b"at some point I got ";
854 let d2 = b"of writing in hex...";
855
856 let mut req = RouterDescRequest::default();
857
858 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
859 digests.push(*d1);
860 }
861 assert!(!req.partial_response_body_ok());
862 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
863 digests.push(*d2);
864 }
865 assert!(req.partial_response_body_ok());
866 assert_eq!(req.max_response_len(), 16 << 10);
867
868 let req = crate::util::encode_request(&req.make_request()?);
869
870 assert_eq!(req,
871 format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
872
873 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
875 let ds: Vec<_> = match req2.requested_descriptors {
876 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
877 RequestedDescs::AllDescriptors => Vec::new(),
878 };
879 assert_eq!(ds, vec![d1, d2]);
880 let req2 = crate::util::encode_request(&req2.make_request()?);
881 assert_eq!(req, req2);
882 Ok(())
883 }
884
885 #[test]
886 #[cfg(feature = "hs-client")]
887 fn test_hs_desc_download_request() -> Result<()> {
888 use tor_llcrypto::pk::ed25519::Ed25519Identity;
889 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
890 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
891 let hsid = HsBlindId::from(hsid);
892 let req = HsDescDownloadRequest::new(hsid);
893 assert!(!req.partial_response_body_ok());
894 assert_eq!(req.max_response_len(), 50 * 1000);
895
896 let req = crate::util::encode_request(&req.make_request()?);
897
898 assert_eq!(
899 req,
900 format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
901 );
902
903 Ok(())
904 }
905}