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]>,
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 uri.push_str(".z");
262
263 let mut req = http::Request::builder().method("GET").uri(uri);
264 req = add_common_headers(req, self.anonymized());
265
266 if let Some(when) = self.last_consensus_date() {
268 req = req.header(
269 http::header::IF_MODIFIED_SINCE,
270 httpdate::fmt_http_date(when),
271 );
272 }
273
274 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
276 req = req.header("X-Or-Diff-From-Consensus", &ids);
277 }
278
279 Ok(req.body(String::new())?)
280 }
281
282 fn partial_response_body_ok(&self) -> bool {
283 false
284 }
285
286 fn check_circuit<'a>(
287 &self,
288 circ: &'a ClientCirc,
289 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
290 let skew_limit = self.skew_limit.clone();
291 Box::pin(async move {
292 use tor_proto::ClockSkew::*;
293 let skew = circ.first_hop_clock_skew().await?;
295 match (&skew_limit, &skew) {
296 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
297 Err(RequestError::TooMuchClockSkew)
298 }
299 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
300 Err(RequestError::TooMuchClockSkew)
301 }
302 (_, _) => Ok(()),
303 }
304 })
305 }
306
307 fn anonymized(&self) -> AnonymizedRequest {
308 AnonymizedRequest::Direct
309 }
310}
311
312#[derive(Debug, Clone, Default)]
314pub struct AuthCertRequest {
315 ids: Vec<AuthCertKeyIds>,
317}
318
319impl AuthCertRequest {
320 pub fn new() -> Self {
322 AuthCertRequest::default()
323 }
324
325 pub fn push(&mut self, ids: AuthCertKeyIds) {
327 self.ids.push(ids);
328 }
329
330 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
332 self.ids.iter()
333 }
334}
335
336impl sealed::RequestableInner for AuthCertRequest {
337 fn make_request(&self) -> Result<http::Request<String>> {
338 if self.ids.is_empty() {
339 return Err(RequestError::EmptyRequest);
340 }
341 let mut ids = self.ids.clone();
342 ids.sort_unstable();
343
344 let ids: Vec<String> = ids
345 .iter()
346 .map(|id| {
347 format!(
348 "{}-{}",
349 hex::encode(id.id_fingerprint.as_bytes()),
350 hex::encode(id.sk_fingerprint.as_bytes())
351 )
352 })
353 .collect();
354
355 let uri = format!("/tor/keys/fp-sk/{}.z", &ids.join("+"));
356
357 let req = http::Request::builder().method("GET").uri(uri);
358 let req = add_common_headers(req, self.anonymized());
359
360 Ok(req.body(String::new())?)
361 }
362
363 fn partial_response_body_ok(&self) -> bool {
364 self.ids.len() > 1
365 }
366
367 fn max_response_len(&self) -> usize {
368 self.ids.len().saturating_mul(16 * 1024)
370 }
371
372 fn anonymized(&self) -> AnonymizedRequest {
373 AnonymizedRequest::Direct
374 }
375}
376
377impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
378 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
379 let mut req = Self::new();
380 for i in iter {
381 req.push(i);
382 }
383 req
384 }
385}
386
387#[derive(Debug, Clone, Default)]
389pub struct MicrodescRequest {
390 digests: Vec<MdDigest>,
392}
393
394impl MicrodescRequest {
395 pub fn new() -> Self {
397 MicrodescRequest::default()
398 }
399 pub fn push(&mut self, d: MdDigest) {
401 self.digests.push(d);
402 }
403
404 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
406 self.digests.iter()
407 }
408}
409
410impl sealed::RequestableInner for MicrodescRequest {
411 fn make_request(&self) -> Result<http::Request<String>> {
412 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
413 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
414 .ok_or(RequestError::EmptyRequest)?;
415 let uri = format!("/tor/micro/d/{}.z", &ids);
416 let req = http::Request::builder().method("GET").uri(uri);
417
418 let req = add_common_headers(req, self.anonymized());
419
420 Ok(req.body(String::new())?)
421 }
422
423 fn partial_response_body_ok(&self) -> bool {
424 self.digests.len() > 1
425 }
426
427 fn max_response_len(&self) -> usize {
428 self.digests.len().saturating_mul(8 * 1024)
430 }
431
432 fn anonymized(&self) -> AnonymizedRequest {
433 AnonymizedRequest::Direct
434 }
435}
436
437impl FromIterator<MdDigest> for MicrodescRequest {
438 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
439 let mut req = Self::new();
440 for i in iter {
441 req.push(i);
442 }
443 req
444 }
445}
446
447#[derive(Debug, Clone)]
449#[cfg(feature = "routerdesc")]
450pub struct RouterDescRequest {
451 requested_descriptors: RequestedDescs,
453}
454
455#[derive(Debug, Clone)]
457#[cfg(feature = "routerdesc")]
458enum RequestedDescs {
459 AllDescriptors,
461 Digests(Vec<RdDigest>),
463}
464
465#[cfg(feature = "routerdesc")]
466impl 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 uri.push_str(".z");
506
507 let req = http::Request::builder().method("GET").uri(uri);
508 let req = add_common_headers(req, self.anonymized());
509
510 Ok(req.body(String::new())?)
511 }
512
513 fn partial_response_body_ok(&self) -> bool {
514 match self.requested_descriptors {
515 RequestedDescs::Digests(ref digests) => digests.len() > 1,
516 RequestedDescs::AllDescriptors => true,
517 }
518 }
519
520 fn max_response_len(&self) -> usize {
521 match self.requested_descriptors {
523 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
524 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, }
526 }
527
528 fn anonymized(&self) -> AnonymizedRequest {
529 AnonymizedRequest::Direct
530 }
531}
532
533#[cfg(feature = "routerdesc")]
534impl FromIterator<RdDigest> for RouterDescRequest {
535 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
536 let digests = iter.into_iter().collect();
537
538 RouterDescRequest {
539 requested_descriptors: RequestedDescs::Digests(digests),
540 }
541 }
542}
543
544#[derive(Debug, Clone, Default)]
546#[cfg(feature = "routerdesc")]
547#[non_exhaustive]
548pub struct RoutersOwnDescRequest {}
549
550#[cfg(feature = "routerdesc")]
551impl RoutersOwnDescRequest {
552 pub fn new() -> Self {
554 RoutersOwnDescRequest::default()
555 }
556}
557
558#[cfg(feature = "routerdesc")]
559impl sealed::RequestableInner for RoutersOwnDescRequest {
560 fn make_request(&self) -> Result<http::Request<String>> {
561 let uri = "/tor/server/authority.z";
562 let req = http::Request::builder().method("GET").uri(uri);
563 let req = add_common_headers(req, self.anonymized());
564
565 Ok(req.body(String::new())?)
566 }
567
568 fn partial_response_body_ok(&self) -> bool {
569 false
570 }
571
572 fn anonymized(&self) -> AnonymizedRequest {
573 AnonymizedRequest::Direct
574 }
575}
576
577#[derive(Debug, Clone)]
581#[cfg(feature = "hs-client")]
582pub struct HsDescDownloadRequest {
583 hsid: HsBlindId,
585 max_len: usize,
587}
588
589#[cfg(feature = "hs-client")]
590impl HsDescDownloadRequest {
591 pub fn new(hsid: HsBlindId) -> Self {
594 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
596 HsDescDownloadRequest {
597 hsid,
598 max_len: DEFAULT_HSDESC_MAX_LEN,
599 }
600 }
601
602 pub fn set_max_len(&mut self, max_len: usize) {
604 self.max_len = max_len;
605 }
606}
607
608#[cfg(feature = "hs-client")]
609impl sealed::RequestableInner for HsDescDownloadRequest {
610 fn make_request(&self) -> Result<http::Request<String>> {
611 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
612 let uri = format!("/tor/hs/3/{}", hsid);
615 let req = http::Request::builder().method("GET").uri(uri);
616 let req = add_common_headers(req, self.anonymized());
617 Ok(req.body(String::new())?)
618 }
619
620 fn partial_response_body_ok(&self) -> bool {
621 false
622 }
623
624 fn max_response_len(&self) -> usize {
625 self.max_len
626 }
627
628 fn anonymized(&self) -> AnonymizedRequest {
629 AnonymizedRequest::Anonymized
630 }
631}
632
633#[derive(Debug, Clone)]
637#[cfg(feature = "hs-service")]
638pub struct HsDescUploadRequest(String);
639
640#[cfg(feature = "hs-service")]
641impl HsDescUploadRequest {
642 pub fn new(hsdesc: String) -> Self {
644 HsDescUploadRequest(hsdesc)
645 }
646}
647
648#[cfg(feature = "hs-service")]
649impl sealed::RequestableInner for HsDescUploadRequest {
650 fn make_request(&self) -> Result<http::Request<String>> {
651 const URI: &str = "/tor/hs/3/publish";
653
654 let req = http::Request::builder().method("POST").uri(URI);
655 let req = add_common_headers(req, self.anonymized());
656 Ok(req.body(self.0.clone())?)
657 }
658
659 fn partial_response_body_ok(&self) -> bool {
660 false
661 }
662
663 fn max_response_len(&self) -> usize {
664 1024
672 }
673
674 fn anonymized(&self) -> AnonymizedRequest {
675 AnonymizedRequest::Anonymized
676 }
677}
678
679const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
681
682fn all_encodings() -> String {
684 #[allow(unused_mut)]
685 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
686 #[cfg(feature = "xz")]
687 {
688 encodings += ", x-tor-lzma";
689 }
690 #[cfg(feature = "zstd")]
691 {
692 encodings += ", x-zstd";
693 }
694
695 encodings
696}
697
698fn add_common_headers(
702 req: http::request::Builder,
703 anon: AnonymizedRequest,
704) -> http::request::Builder {
705 match anon {
707 AnonymizedRequest::Anonymized => {
708 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
711 }
712 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
713 }
714}
715
716#[cfg(test)]
717mod test {
718 #![allow(clippy::bool_assert_comparison)]
720 #![allow(clippy::clone_on_copy)]
721 #![allow(clippy::dbg_macro)]
722 #![allow(clippy::mixed_attributes_style)]
723 #![allow(clippy::print_stderr)]
724 #![allow(clippy::print_stdout)]
725 #![allow(clippy::single_char_pattern)]
726 #![allow(clippy::unwrap_used)]
727 #![allow(clippy::unchecked_duration_subtraction)]
728 #![allow(clippy::useless_vec)]
729 #![allow(clippy::needless_pass_by_value)]
730 use super::sealed::RequestableInner;
732 use super::*;
733
734 #[test]
735 fn test_md_request() -> Result<()> {
736 let d1 = b"This is a testing digest. it isn";
737 let d2 = b"'t actually SHA-256.............";
738
739 let mut req = MicrodescRequest::default();
740 req.push(*d1);
741 assert!(!req.partial_response_body_ok());
742 req.push(*d2);
743 assert!(req.partial_response_body_ok());
744 assert_eq!(req.max_response_len(), 16 << 10);
745
746 let req = crate::util::encode_request(&req.make_request()?);
747
748 assert_eq!(req,
749 format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
750
751 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
753 let ds: Vec<_> = req2.digests().collect();
754 assert_eq!(ds, vec![d1, d2]);
755 let req2 = crate::util::encode_request(&req2.make_request()?);
756 assert_eq!(req, req2);
757
758 Ok(())
759 }
760
761 #[test]
762 fn test_cert_request() -> Result<()> {
763 let d1 = b"This is a testing dn";
764 let d2 = b"'t actually SHA-256.";
765 let key1 = AuthCertKeyIds {
766 id_fingerprint: (*d1).into(),
767 sk_fingerprint: (*d2).into(),
768 };
769
770 let d3 = b"blah blah blah 1 2 3";
771 let d4 = b"I like pizza from Na";
772 let key2 = AuthCertKeyIds {
773 id_fingerprint: (*d3).into(),
774 sk_fingerprint: (*d4).into(),
775 };
776
777 let mut req = AuthCertRequest::default();
778 req.push(key1);
779 assert!(!req.partial_response_body_ok());
780 req.push(key2);
781 assert!(req.partial_response_body_ok());
782 assert_eq!(req.max_response_len(), 32 << 10);
783
784 let keys: Vec<_> = req.keys().collect();
785 assert_eq!(keys, vec![&key1, &key2]);
786
787 let req = crate::util::encode_request(&req.make_request()?);
788
789 assert_eq!(req,
790 format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
791
792 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
793 let req2 = crate::util::encode_request(&req2.make_request()?);
794 assert_eq!(req, req2);
795
796 Ok(())
797 }
798
799 #[test]
800 fn test_consensus_request() -> Result<()> {
801 let d1 = RsaIdentity::from_bytes(
802 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
803 )
804 .unwrap();
805
806 let d2 = b"blah blah blah 12 blah blah blah";
807 let d3 = SystemTime::now();
808 let mut req = ConsensusRequest::default();
809
810 let when = httpdate::fmt_http_date(d3);
811
812 req.push_authority_id(d1);
813 req.push_old_consensus_digest(*d2);
814 req.set_last_consensus_date(d3);
815 assert!(!req.partial_response_body_ok());
816 assert_eq!(req.max_response_len(), (16 << 20) - 1);
817 assert_eq!(req.old_consensus_digests().next(), Some(d2));
818 assert_eq!(req.authority_ids().next(), Some(&d1));
819 assert_eq!(req.last_consensus_date(), Some(d3));
820
821 let req = crate::util::encode_request(&req.make_request()?);
822
823 assert_eq!(req,
824 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));
825
826 let req = ConsensusRequest::default();
828 let req = crate::util::encode_request(&req.make_request()?);
829 assert_eq!(req,
830 format!("GET /tor/status-vote/current/consensus-microdesc.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
831
832 Ok(())
833 }
834
835 #[test]
836 #[cfg(feature = "routerdesc")]
837 fn test_rd_request_all() -> Result<()> {
838 let req = RouterDescRequest::all();
839 assert!(req.partial_response_body_ok());
840 assert_eq!(req.max_response_len(), 1 << 26);
841
842 let req = crate::util::encode_request(&req.make_request()?);
843
844 assert_eq!(
845 req,
846 format!(
847 "GET /tor/server/all.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
848 all_encodings()
849 )
850 );
851
852 Ok(())
853 }
854
855 #[test]
856 #[cfg(feature = "routerdesc")]
857 fn test_rd_request() -> Result<()> {
858 let d1 = b"at some point I got ";
859 let d2 = b"of writing in hex...";
860
861 let mut req = RouterDescRequest::default();
862
863 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
864 digests.push(*d1);
865 }
866 assert!(!req.partial_response_body_ok());
867 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
868 digests.push(*d2);
869 }
870 assert!(req.partial_response_body_ok());
871 assert_eq!(req.max_response_len(), 16 << 10);
872
873 let req = crate::util::encode_request(&req.make_request()?);
874
875 assert_eq!(req,
876 format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e.z HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
877
878 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
880 let ds: Vec<_> = match req2.requested_descriptors {
881 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
882 RequestedDescs::AllDescriptors => Vec::new(),
883 };
884 assert_eq!(ds, vec![d1, d2]);
885 let req2 = crate::util::encode_request(&req2.make_request()?);
886 assert_eq!(req, req2);
887 Ok(())
888 }
889
890 #[test]
891 #[cfg(feature = "hs-client")]
892 fn test_hs_desc_download_request() -> Result<()> {
893 use tor_llcrypto::pk::ed25519::Ed25519Identity;
894 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
895 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
896 let hsid = HsBlindId::from(hsid);
897 let req = HsDescDownloadRequest::new(hsid);
898 assert!(!req.partial_response_body_ok());
899 assert_eq!(req.max_response_len(), 50 * 1000);
900
901 let req = crate::util::encode_request(&req.make_request()?);
902
903 assert_eq!(
904 req,
905 format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
906 );
907
908 Ok(())
909 }
910}