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::sync::Arc;
24use std::time::{Duration, SystemTime};
25
26use itertools::Itertools;
27
28use crate::AnonymizedRequest;
29use crate::body::RequestBody;
30use crate::err::RequestError;
31
32pub(crate) mod sealed {
34 use tor_circmgr::ClientDirTunnel;
35
36 use crate::body::RequestBody;
37
38 use super::{AnonymizedRequest, Result};
39
40 use std::future::Future;
41 use std::pin::Pin;
42
43 pub trait RequestableInner: Send + Sync {
46 fn make_request(&self) -> Result<http::Request<RequestBody>>;
49
50 fn partial_response_body_ok(&self) -> bool;
56
57 fn max_response_len(&self) -> usize {
60 (16 * 1024 * 1024) - 1
61 }
62
63 fn check_circuit<'a>(
66 &self,
67 tunnel: &'a ClientDirTunnel,
68 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
69 let _ = tunnel;
70 Box::pin(async { Ok(()) })
71 }
72
73 fn anonymized(&self) -> AnonymizedRequest;
75 }
76}
77
78pub trait Requestable: sealed::RequestableInner {
80 fn debug_request(&self) -> DisplayRequestable<'_, Self>
85 where
86 Self: Sized,
87 {
88 DisplayRequestable(self)
89 }
90}
91impl<T: sealed::RequestableInner> Requestable for T {}
92
93pub struct DisplayRequestable<'a, R: Requestable>(&'a R);
95
96impl<'a, R: Requestable> std::fmt::Debug for DisplayRequestable<'a, R> {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(f, "{:?}", self.0.make_request())
99 }
100}
101
102impl sealed::RequestableInner for Arc<dyn Requestable> {
103 fn make_request(&self) -> Result<http::Request<RequestBody>> {
104 let r: &dyn Requestable = self.as_ref();
105 r.make_request()
106 }
107
108 fn partial_response_body_ok(&self) -> bool {
109 let r: &dyn Requestable = self.as_ref();
110 r.partial_response_body_ok()
111 }
112
113 fn anonymized(&self) -> AnonymizedRequest {
114 let r: &dyn Requestable = self.as_ref();
115 r.anonymized()
116 }
117}
118
119#[derive(Clone, Debug)]
126struct SkewLimit {
127 max_fast: Duration,
132
133 max_slow: Duration,
139}
140
141#[derive(Debug, Clone)]
143pub struct ConsensusRequest {
144 flavor: ConsensusFlavor,
147 authority_ids: Vec<RsaIdentity>,
151 last_consensus_published: Option<SystemTime>,
155 last_consensus_sha3_256: Vec<[u8; 32]>,
158 skew_limit: Option<SkewLimit>,
160}
161
162impl ConsensusRequest {
163 pub fn new(flavor: ConsensusFlavor) -> Self {
165 ConsensusRequest {
166 flavor,
167 authority_ids: Vec::new(),
168 last_consensus_published: None,
169 last_consensus_sha3_256: Vec::new(),
170 skew_limit: None,
171 }
172 }
173
174 pub fn push_authority_id(&mut self, id: RsaIdentity) {
177 self.authority_ids.push(id);
178 }
179
180 pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
183 self.last_consensus_sha3_256.push(d);
184 }
185
186 pub fn set_last_consensus_date(&mut self, when: SystemTime) {
189 self.last_consensus_published = Some(when);
190 }
191
192 pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
195 self.last_consensus_sha3_256.iter()
196 }
197
198 pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
201 self.authority_ids.iter()
202 }
203
204 pub fn last_consensus_date(&self) -> Option<SystemTime> {
206 self.last_consensus_published
207 }
208
209 pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
220 self.skew_limit = Some(SkewLimit { max_fast, max_slow });
221 }
222}
223
224fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
234where
235 DL: IntoIterator<Item = &'d D> + 'd,
236 D: PartialOrd + Ord + 'd,
237 EF: Fn(&'d D) -> String,
238{
239 let mut digests = digests.into_iter().collect_vec();
240 if digests.is_empty() {
241 return None;
242 }
243 digests.sort_unstable();
244 let ids = digests.into_iter().map(encode).map(Cow::Owned);
245 let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
248 Some(ids)
249}
250
251impl Default for ConsensusRequest {
252 fn default() -> Self {
253 Self::new(ConsensusFlavor::Microdesc)
254 }
255}
256
257impl sealed::RequestableInner for ConsensusRequest {
258 fn make_request(&self) -> Result<http::Request<RequestBody>> {
259 let mut uri = "/tor/status-vote/current/consensus".to_string();
261 match self.flavor {
262 ConsensusFlavor::Plain => {}
263 flav => {
264 uri.push('-');
265 uri.push_str(flav.name());
266 }
267 }
268 let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
269 if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
270 uri.push('/');
272 uri.push_str(&ids);
273 }
274 let mut req = http::Request::builder().method("GET").uri(uri);
277 req = add_common_headers(req, self.anonymized());
278
279 if let Some(when) = self.last_consensus_date() {
281 req = req.header(
282 http::header::IF_MODIFIED_SINCE,
283 httpdate::fmt_http_date(when),
284 );
285 }
286
287 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
289 req = req.header("X-Or-Diff-From-Consensus", &ids);
290 }
291
292 Ok(req.body(RequestBody::default())?)
293 }
294
295 fn partial_response_body_ok(&self) -> bool {
296 false
297 }
298
299 fn check_circuit<'a>(
300 &self,
301 tunnel: &'a ClientDirTunnel,
302 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
303 let skew_limit = self.skew_limit.clone();
304 Box::pin(async move {
305 use tor_proto::ClockSkew::*;
306 let skew = tunnel.first_hop_clock_skew().await?;
308 match (&skew_limit, &skew) {
309 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
310 Err(RequestError::TooMuchClockSkew)
311 }
312 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
313 Err(RequestError::TooMuchClockSkew)
314 }
315 (_, _) => Ok(()),
316 }
317 })
318 }
319
320 fn anonymized(&self) -> AnonymizedRequest {
321 AnonymizedRequest::Direct
322 }
323}
324
325#[derive(Debug, Clone, Default)]
327pub struct AuthCertRequest {
328 ids: Vec<AuthCertKeyIds>,
330}
331
332impl AuthCertRequest {
333 pub fn new() -> Self {
335 AuthCertRequest::default()
336 }
337
338 pub fn push(&mut self, ids: AuthCertKeyIds) {
340 self.ids.push(ids);
341 }
342
343 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
345 self.ids.iter()
346 }
347}
348
349impl sealed::RequestableInner for AuthCertRequest {
350 fn make_request(&self) -> Result<http::Request<RequestBody>> {
351 if self.ids.is_empty() {
352 return Err(RequestError::EmptyRequest);
353 }
354 let mut ids = self.ids.clone();
355 ids.sort_unstable();
356
357 let ids: Vec<String> = ids
358 .iter()
359 .map(|id| {
360 format!(
361 "{}-{}",
362 hex::encode(id.id_fingerprint.as_bytes()),
363 hex::encode(id.sk_fingerprint.as_bytes())
364 )
365 })
366 .collect();
367
368 let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
369
370 let req = http::Request::builder().method("GET").uri(uri);
371 let req = add_common_headers(req, self.anonymized());
372
373 Ok(req.body(RequestBody::default())?)
374 }
375
376 fn partial_response_body_ok(&self) -> bool {
377 self.ids.len() > 1
378 }
379
380 fn max_response_len(&self) -> usize {
381 self.ids.len().saturating_mul(16 * 1024)
383 }
384
385 fn anonymized(&self) -> AnonymizedRequest {
386 AnonymizedRequest::Direct
387 }
388}
389
390impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
391 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
392 let mut req = Self::new();
393 for i in iter {
394 req.push(i);
395 }
396 req
397 }
398}
399
400#[derive(Debug, Clone, Default)]
402pub struct MicrodescRequest {
403 digests: Vec<MdDigest>,
405}
406
407impl MicrodescRequest {
408 pub fn new() -> Self {
410 MicrodescRequest::default()
411 }
412 pub fn push(&mut self, d: MdDigest) {
414 self.digests.push(d);
415 }
416
417 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
419 self.digests.iter()
420 }
421}
422
423impl sealed::RequestableInner for MicrodescRequest {
424 fn make_request(&self) -> Result<http::Request<RequestBody>> {
425 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
426 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
427 .ok_or(RequestError::EmptyRequest)?;
428 let uri = format!("/tor/micro/d/{}", &ids);
429 let req = http::Request::builder().method("GET").uri(uri);
430
431 let req = add_common_headers(req, self.anonymized());
432
433 Ok(req.body(RequestBody::default())?)
434 }
435
436 fn partial_response_body_ok(&self) -> bool {
437 self.digests.len() > 1
438 }
439
440 fn max_response_len(&self) -> usize {
441 self.digests.len().saturating_mul(8 * 1024)
443 }
444
445 fn anonymized(&self) -> AnonymizedRequest {
446 AnonymizedRequest::Direct
447 }
448}
449
450impl FromIterator<MdDigest> for MicrodescRequest {
451 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
452 let mut req = Self::new();
453 for i in iter {
454 req.push(i);
455 }
456 req
457 }
458}
459
460#[derive(Debug, Clone)]
462#[cfg(feature = "routerdesc")]
463pub struct RouterDescRequest {
464 requested_descriptors: RequestedDescs,
466}
467
468#[derive(Debug, Clone)]
470#[cfg(feature = "routerdesc")]
471enum RequestedDescs {
472 AllDescriptors,
474 Digests(Vec<RdDigest>),
476}
477
478#[cfg(feature = "routerdesc")]
479impl Default for RouterDescRequest {
481 fn default() -> Self {
482 RouterDescRequest {
483 requested_descriptors: RequestedDescs::Digests(Vec::new()),
484 }
485 }
486}
487
488#[cfg(feature = "routerdesc")]
489impl RouterDescRequest {
490 pub fn all() -> Self {
492 RouterDescRequest {
493 requested_descriptors: RequestedDescs::AllDescriptors,
494 }
495 }
496 pub fn new() -> Self {
498 RouterDescRequest::default()
499 }
500}
501
502#[cfg(feature = "routerdesc")]
503impl sealed::RequestableInner for RouterDescRequest {
504 fn make_request(&self) -> Result<http::Request<RequestBody>> {
505 let mut uri = "/tor/server/".to_string();
506
507 match self.requested_descriptors {
508 RequestedDescs::Digests(ref digests) => {
509 uri.push_str("d/");
510 let ids = digest_list_stringify(digests, hex::encode, "+")
511 .ok_or(RequestError::EmptyRequest)?;
512 uri.push_str(&ids);
513 }
514 RequestedDescs::AllDescriptors => {
515 uri.push_str("all");
516 }
517 }
518
519 let req = http::Request::builder().method("GET").uri(uri);
520 let req = add_common_headers(req, self.anonymized());
521
522 Ok(req.body(RequestBody::default())?)
523 }
524
525 fn partial_response_body_ok(&self) -> bool {
526 match self.requested_descriptors {
527 RequestedDescs::Digests(ref digests) => digests.len() > 1,
528 RequestedDescs::AllDescriptors => true,
529 }
530 }
531
532 fn max_response_len(&self) -> usize {
533 match self.requested_descriptors {
535 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
536 RequestedDescs::AllDescriptors => 64 * 1024 * 1024, }
538 }
539
540 fn anonymized(&self) -> AnonymizedRequest {
541 AnonymizedRequest::Direct
542 }
543}
544
545#[cfg(feature = "routerdesc")]
546impl FromIterator<RdDigest> for RouterDescRequest {
547 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
548 let digests = iter.into_iter().collect();
549
550 RouterDescRequest {
551 requested_descriptors: RequestedDescs::Digests(digests),
552 }
553 }
554}
555
556#[derive(Debug, Clone, Default)]
558#[cfg(feature = "routerdesc")]
559#[non_exhaustive]
560pub struct RoutersOwnDescRequest {}
561
562#[cfg(feature = "routerdesc")]
563impl RoutersOwnDescRequest {
564 pub fn new() -> Self {
566 RoutersOwnDescRequest::default()
567 }
568}
569
570#[cfg(feature = "routerdesc")]
571impl sealed::RequestableInner for RoutersOwnDescRequest {
572 fn make_request(&self) -> Result<http::Request<RequestBody>> {
573 let uri = "/tor/server/authority";
574 let req = http::Request::builder().method("GET").uri(uri);
575 let req = add_common_headers(req, self.anonymized());
576
577 Ok(req.body(RequestBody::default())?)
578 }
579
580 fn partial_response_body_ok(&self) -> bool {
581 false
582 }
583
584 fn anonymized(&self) -> AnonymizedRequest {
585 AnonymizedRequest::Direct
586 }
587}
588
589#[derive(Debug, Clone)]
594#[cfg(feature = "routerdesc")]
595pub struct ExtraInfoRequest {
596 requested_extra_infos: RequestedExtraInfos,
598}
599
600#[derive(Debug, Clone)]
604#[cfg(feature = "routerdesc")]
605#[non_exhaustive]
606enum RequestedExtraInfos {
607 AllExtraInfos,
612 Digests(Vec<ExtraInfoDigest>),
616}
617
618#[cfg(feature = "routerdesc")]
619impl Default for ExtraInfoRequest {
620 fn default() -> Self {
622 Self {
623 requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
624 }
625 }
626}
627
628#[cfg(feature = "routerdesc")]
629impl ExtraInfoRequest {
630 pub fn all() -> Self {
632 Self {
633 requested_extra_infos: RequestedExtraInfos::AllExtraInfos,
634 }
635 }
636 pub fn new() -> Self {
638 Self::default()
639 }
640}
641
642#[cfg(feature = "routerdesc")]
643impl sealed::RequestableInner for ExtraInfoRequest {
644 fn make_request(&self) -> Result<http::Request<RequestBody>> {
645 let mut uri = "/tor/extra/".to_string();
646
647 match &self.requested_extra_infos {
648 RequestedExtraInfos::AllExtraInfos => uri.push_str("all"),
649 RequestedExtraInfos::Digests(digests) => {
650 uri.push_str("d/");
651 let ids = digest_list_stringify(digests, hex::encode_upper, "+")
652 .ok_or(RequestError::EmptyRequest)?;
653 uri.push_str(&ids);
654 }
655 }
656
657 let req = http::Request::builder().method("GET").uri(uri);
658 let req = add_common_headers(req, self.anonymized());
659 Ok(req.body(RequestBody::default())?)
660 }
661
662 fn partial_response_body_ok(&self) -> bool {
663 match &self.requested_extra_infos {
664 RequestedExtraInfos::Digests(digests) => digests.len() > 1,
665 RequestedExtraInfos::AllExtraInfos => true,
666 }
667 }
668
669 fn max_response_len(&self) -> usize {
670 match &self.requested_extra_infos {
673 RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
674 RequestedExtraInfos::AllExtraInfos => 128 * 1024 * 1024,
675 }
676 }
677
678 fn anonymized(&self) -> AnonymizedRequest {
679 AnonymizedRequest::Direct
680 }
681}
682
683#[cfg(feature = "routerdesc")]
684impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
685 fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
686 Self {
687 requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
688 }
689 }
690}
691
692#[derive(Debug, Clone)]
696#[cfg(feature = "hs-client")]
697pub struct HsDescDownloadRequest {
698 hsid: HsBlindId,
700 max_len: usize,
702}
703
704#[cfg(feature = "hs-client")]
705impl HsDescDownloadRequest {
706 pub fn new(hsid: HsBlindId) -> Self {
709 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
711 HsDescDownloadRequest {
712 hsid,
713 max_len: DEFAULT_HSDESC_MAX_LEN,
714 }
715 }
716
717 pub fn set_max_len(&mut self, max_len: usize) {
719 self.max_len = max_len;
720 }
721}
722
723#[cfg(feature = "hs-client")]
724impl sealed::RequestableInner for HsDescDownloadRequest {
725 fn make_request(&self) -> Result<http::Request<RequestBody>> {
726 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
727 let uri = format!("/tor/hs/3/{}", hsid);
730 let req = http::Request::builder().method("GET").uri(uri);
731 let req = add_common_headers(req, self.anonymized());
732 Ok(req.body(RequestBody::default())?)
733 }
734
735 fn partial_response_body_ok(&self) -> bool {
736 false
737 }
738
739 fn max_response_len(&self) -> usize {
740 self.max_len
741 }
742
743 fn anonymized(&self) -> AnonymizedRequest {
744 AnonymizedRequest::Anonymized
745 }
746}
747
748#[allow(unused)]
750macro_rules! upload_request {
751 {
752 $(
753 $(#[$m:meta])*
754 pub struct $t:ident (
755 $anonymity:ident,
758 $uri:expr,
761 $max_response_len:expr
768 )
769 );*
770 $(;)?
771 } => {
772 $(
773 $(#[$m])*
774 #[derive(Clone, Debug)]
775 pub struct $t(Arc<str>);
776
777 impl $t {
778 pub fn new(document: Arc<str>) -> Self {
780 Self(document)
781 }
782 }
783
784 impl sealed::RequestableInner for $t {
785 fn make_request(&self) -> Result<http::Request<RequestBody>> {
786 const URI: &str = $uri;
788
789 let req = http::Request::builder().method("POST").uri(URI);
790 let req = add_common_headers(req, self.anonymized());
791 Ok(req.body(RequestBody::from(Arc::clone(&self.0)))?)
792 }
793
794 fn partial_response_body_ok(&self) -> bool {
795 false
796 }
797
798 fn max_response_len(&self) -> usize {
799 $max_response_len
800 }
801
802 fn anonymized(&self) -> AnonymizedRequest {
803 AnonymizedRequest::$anonymity
804 }
805 }
806 )*
807 }
808}
809
810#[cfg(feature = "hs-service")]
811upload_request! {
812
813 pub struct HsDescUploadRequest(
817 Anonymized,
818 "/tor/hs/3/publish",
819 1024
827 )
828}
829
830#[cfg(feature = "relay")]
831upload_request! {
832 pub struct UploadRouterDesc(Direct, "/tor/", 4096);
834
835}
836
837const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
839
840fn all_encodings() -> String {
842 #[allow(unused_mut)]
843 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
844 #[cfg(feature = "xz")]
845 {
846 encodings += ", x-tor-lzma";
847 }
848 #[cfg(feature = "zstd")]
849 {
850 encodings += ", x-zstd";
851 }
852
853 encodings
854}
855
856fn add_common_headers(
860 req: http::request::Builder,
861 anon: AnonymizedRequest,
862) -> http::request::Builder {
863 match anon {
865 AnonymizedRequest::Anonymized => {
866 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
869 }
870 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
871 }
872}
873
874#[cfg(test)]
875mod test {
876 #![allow(clippy::bool_assert_comparison)]
878 #![allow(clippy::clone_on_copy)]
879 #![allow(clippy::dbg_macro)]
880 #![allow(clippy::mixed_attributes_style)]
881 #![allow(clippy::print_stderr)]
882 #![allow(clippy::print_stdout)]
883 #![allow(clippy::single_char_pattern)]
884 #![allow(clippy::unwrap_used)]
885 #![allow(clippy::unchecked_time_subtraction)]
886 #![allow(clippy::useless_vec)]
887 #![allow(clippy::needless_pass_by_value)]
888 use super::sealed::RequestableInner;
890 use super::*;
891 use web_time_compat::SystemTimeExt;
892
893 #[test]
894 fn test_md_request() -> Result<()> {
895 let d1 = b"This is a testing digest. it isn";
896 let d2 = b"'t actually SHA-256.............";
897
898 let mut req = MicrodescRequest::default();
899 req.push(*d1);
900 assert!(!req.partial_response_body_ok());
901 req.push(*d2);
902 assert!(req.partial_response_body_ok());
903 assert_eq!(req.max_response_len(), 16 << 10);
904
905 let req = crate::util::request_to_string(&req.make_request()?);
906
907 assert_eq!(
908 req,
909 format!(
910 "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
911 all_encodings()
912 )
913 );
914
915 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
917 let ds: Vec<_> = req2.digests().collect();
918 assert_eq!(ds, vec![d1, d2]);
919 let req2 = crate::util::request_to_string(&req2.make_request()?);
920 assert_eq!(req, req2);
921
922 Ok(())
923 }
924
925 #[test]
926 fn test_cert_request() -> Result<()> {
927 let d1 = b"This is a testing dn";
928 let d2 = b"'t actually SHA-256.";
929 let key1 = AuthCertKeyIds {
930 id_fingerprint: (*d1).into(),
931 sk_fingerprint: (*d2).into(),
932 };
933
934 let d3 = b"blah blah blah 1 2 3";
935 let d4 = b"I like pizza from Na";
936 let key2 = AuthCertKeyIds {
937 id_fingerprint: (*d3).into(),
938 sk_fingerprint: (*d4).into(),
939 };
940
941 let mut req = AuthCertRequest::default();
942 req.push(key1);
943 assert!(!req.partial_response_body_ok());
944 req.push(key2);
945 assert!(req.partial_response_body_ok());
946 assert_eq!(req.max_response_len(), 32 << 10);
947
948 let keys: Vec<_> = req.keys().collect();
949 assert_eq!(keys, vec![&key1, &key2]);
950
951 let req = crate::util::request_to_string(&req.make_request()?);
952
953 assert_eq!(
954 req,
955 format!(
956 "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
957 all_encodings()
958 )
959 );
960
961 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
962 let req2 = crate::util::request_to_string(&req2.make_request()?);
963 assert_eq!(req, req2);
964
965 Ok(())
966 }
967
968 #[test]
969 fn test_consensus_request() -> Result<()> {
970 let d1 = RsaIdentity::from_bytes(
971 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
972 )
973 .unwrap();
974
975 let d2 = b"blah blah blah 12 blah blah blah";
976 let d3 = SystemTime::get();
977 let mut req = ConsensusRequest::default();
978
979 let when = httpdate::fmt_http_date(d3);
980
981 req.push_authority_id(d1);
982 req.push_old_consensus_digest(*d2);
983 req.set_last_consensus_date(d3);
984 assert!(!req.partial_response_body_ok());
985 assert_eq!(req.max_response_len(), (16 << 20) - 1);
986 assert_eq!(req.old_consensus_digests().next(), Some(d2));
987 assert_eq!(req.authority_ids().next(), Some(&d1));
988 assert_eq!(req.last_consensus_date(), Some(d3));
989
990 let req = crate::util::request_to_string(&req.make_request()?);
991
992 assert_eq!(
993 req,
994 format!(
995 "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",
996 all_encodings(),
997 when
998 )
999 );
1000
1001 let req = ConsensusRequest::default();
1003 let req = crate::util::request_to_string(&req.make_request()?);
1004 assert_eq!(
1005 req,
1006 format!(
1007 "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1008 all_encodings()
1009 )
1010 );
1011
1012 Ok(())
1013 }
1014
1015 #[test]
1016 #[cfg(feature = "routerdesc")]
1017 fn test_rd_request_all() -> Result<()> {
1018 let req = RouterDescRequest::all();
1019 assert!(req.partial_response_body_ok());
1020 assert_eq!(req.max_response_len(), 1 << 26);
1021
1022 let req = crate::util::request_to_string(&req.make_request()?);
1023
1024 assert_eq!(
1025 req,
1026 format!(
1027 "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1028 all_encodings()
1029 )
1030 );
1031
1032 Ok(())
1033 }
1034
1035 #[test]
1036 #[cfg(feature = "routerdesc")]
1037 fn test_rd_request() -> Result<()> {
1038 let d1 = b"at some point I got ";
1039 let d2 = b"of writing in hex...";
1040
1041 let mut req = RouterDescRequest::default();
1042
1043 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
1044 digests.push(*d1);
1045 }
1046 assert!(!req.partial_response_body_ok());
1047 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
1048 digests.push(*d2);
1049 }
1050 assert!(req.partial_response_body_ok());
1051 assert_eq!(req.max_response_len(), 16 << 10);
1052
1053 let req = crate::util::request_to_string(&req.make_request()?);
1054
1055 assert_eq!(
1056 req,
1057 format!(
1058 "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1059 all_encodings()
1060 )
1061 );
1062
1063 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
1065 let ds: Vec<_> = match req2.requested_descriptors {
1066 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
1067 RequestedDescs::AllDescriptors => Vec::new(),
1068 };
1069 assert_eq!(ds, vec![d1, d2]);
1070 let req2 = crate::util::request_to_string(&req2.make_request()?);
1071 assert_eq!(req, req2);
1072 Ok(())
1073 }
1074
1075 #[test]
1076 #[cfg(feature = "routerdesc")]
1077 fn test_extra_info_request() -> Result<()> {
1078 let req = ExtraInfoRequest::from_iter([[0; 20], [1; 20], [2; 20]]);
1079 assert_eq!(
1080 crate::util::request_to_string(&req.make_request()?),
1081 format!(
1082 "GET /tor/extra/d/{}+{}+{} HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1083 hex::encode_upper([0; 20]),
1084 hex::encode_upper([1; 20]),
1085 hex::encode_upper([2; 20]),
1086 all_encodings()
1087 )
1088 );
1089
1090 let req = ExtraInfoRequest::all();
1091 assert_eq!(
1092 crate::util::request_to_string(&req.make_request()?),
1093 format!(
1094 "GET /tor/extra/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1095 all_encodings()
1096 )
1097 );
1098 Ok(())
1099 }
1100
1101 #[test]
1102 #[cfg(feature = "hs-client")]
1103 fn test_hs_desc_download_request() -> Result<()> {
1104 use tor_llcrypto::pk::ed25519::Ed25519Identity;
1105 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
1106 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
1107 let hsid = HsBlindId::from(hsid);
1108 let req = HsDescDownloadRequest::new(hsid);
1109 assert!(!req.partial_response_body_ok());
1110 assert_eq!(req.max_response_len(), 50 * 1000);
1111
1112 let req = crate::util::request_to_string(&req.make_request()?);
1113
1114 assert_eq!(
1115 req,
1116 format!(
1117 "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1118 UNIVERSAL_ENCODINGS
1119 )
1120 );
1121
1122 Ok(())
1123 }
1124}