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
61 fn check_circuit_skip_optimization<'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 fn max_response_len(&self) -> usize {
119 let r: &dyn Requestable = self.as_ref();
120 r.max_response_len()
121 }
122
123 fn check_circuit_skip_optimization<'a>(
124 &self,
125 tunnel: &'a ClientDirTunnel,
126 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
127 let r: &dyn Requestable = self.as_ref();
128 r.check_circuit_skip_optimization(tunnel)
129 }
130}
131
132#[derive(Clone, Debug)]
139struct SkewLimit {
140 max_fast: Duration,
145
146 max_slow: Duration,
152}
153
154#[derive(Debug, Clone)]
156pub struct ConsensusRequest {
157 flavor: ConsensusFlavor,
160 authority_ids: Vec<RsaIdentity>,
164 last_consensus_published: Option<SystemTime>,
168 last_consensus_sha3_256: Vec<[u8; 32]>,
171 skew_limit: Option<SkewLimit>,
173}
174
175impl ConsensusRequest {
176 pub fn new(flavor: ConsensusFlavor) -> Self {
178 ConsensusRequest {
179 flavor,
180 authority_ids: Vec::new(),
181 last_consensus_published: None,
182 last_consensus_sha3_256: Vec::new(),
183 skew_limit: None,
184 }
185 }
186
187 pub fn push_authority_id(&mut self, id: RsaIdentity) {
190 self.authority_ids.push(id);
191 }
192
193 pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
196 self.last_consensus_sha3_256.push(d);
197 }
198
199 pub fn set_last_consensus_date(&mut self, when: SystemTime) {
202 self.last_consensus_published = Some(when);
203 }
204
205 pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
208 self.last_consensus_sha3_256.iter()
209 }
210
211 pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
214 self.authority_ids.iter()
215 }
216
217 pub fn last_consensus_date(&self) -> Option<SystemTime> {
219 self.last_consensus_published
220 }
221
222 pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
233 self.skew_limit = Some(SkewLimit { max_fast, max_slow });
234 }
235}
236
237fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
247where
248 DL: IntoIterator<Item = &'d D> + 'd,
249 D: PartialOrd + Ord + 'd,
250 EF: Fn(&'d D) -> String,
251{
252 let mut digests = digests.into_iter().collect_vec();
253 if digests.is_empty() {
254 return None;
255 }
256 digests.sort_unstable();
257 let ids = digests.into_iter().map(encode).map(Cow::Owned);
258 let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
261 Some(ids)
262}
263
264impl Default for ConsensusRequest {
265 fn default() -> Self {
266 Self::new(ConsensusFlavor::Microdesc)
267 }
268}
269
270impl sealed::RequestableInner for ConsensusRequest {
271 fn make_request(&self) -> Result<http::Request<RequestBody>> {
272 let mut uri = "/tor/status-vote/current/consensus".to_string();
274 match self.flavor {
275 ConsensusFlavor::Plain => {}
276 flav => {
277 uri.push('-');
278 uri.push_str(flav.name());
279 }
280 }
281 let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
282 if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
283 uri.push('/');
285 uri.push_str(&ids);
286 }
287 let mut req = http::Request::builder().method("GET").uri(uri);
290 req = add_common_headers(req, self.anonymized());
291
292 if let Some(when) = self.last_consensus_date() {
294 req = req.header(
295 http::header::IF_MODIFIED_SINCE,
296 httpdate::fmt_http_date(when),
297 );
298 }
299
300 if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
302 req = req.header("X-Or-Diff-From-Consensus", &ids);
303 }
304
305 Ok(req.body(RequestBody::default())?)
306 }
307
308 fn partial_response_body_ok(&self) -> bool {
309 false
310 }
311
312 fn max_response_len(&self) -> usize {
313 (16 * 1024 * 1024) - 1
314 }
315
316 fn check_circuit_skip_optimization<'a>(
317 &self,
318 tunnel: &'a ClientDirTunnel,
319 ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
320 let skew_limit = self.skew_limit.clone();
321 Box::pin(async move {
322 use tor_proto::ClockSkew::*;
323 let skew = tunnel.first_hop_clock_skew().await?;
325 match (&skew_limit, &skew) {
326 (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
327 Err(RequestError::TooMuchClockSkew)
328 }
329 (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
330 Err(RequestError::TooMuchClockSkew)
331 }
332 (_, _) => Ok(()),
333 }
334 })
335 }
336
337 fn anonymized(&self) -> AnonymizedRequest {
338 AnonymizedRequest::Direct
339 }
340}
341
342#[derive(Debug, Clone, Default)]
344pub struct AuthCertRequest {
345 ids: Vec<AuthCertKeyIds>,
347}
348
349impl AuthCertRequest {
350 pub fn new() -> Self {
352 AuthCertRequest::default()
353 }
354
355 pub fn push(&mut self, ids: AuthCertKeyIds) {
357 self.ids.push(ids);
358 }
359
360 pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
362 self.ids.iter()
363 }
364}
365
366impl sealed::RequestableInner for AuthCertRequest {
367 fn make_request(&self) -> Result<http::Request<RequestBody>> {
368 if self.ids.is_empty() {
369 return Err(RequestError::EmptyRequest);
370 }
371 let mut ids = self.ids.clone();
372 ids.sort_unstable();
373
374 let ids: Vec<String> = ids
375 .iter()
376 .map(|id| {
377 format!(
378 "{}-{}",
379 hex::encode(id.id_fingerprint.as_bytes()),
380 hex::encode(id.sk_fingerprint.as_bytes())
381 )
382 })
383 .collect();
384
385 let uri = format!("/tor/keys/fp-sk/{}", ids.join("+"));
386
387 let req = http::Request::builder().method("GET").uri(uri);
388 let req = add_common_headers(req, self.anonymized());
389
390 Ok(req.body(RequestBody::default())?)
391 }
392
393 fn partial_response_body_ok(&self) -> bool {
394 self.ids.len() > 1
395 }
396
397 fn max_response_len(&self) -> usize {
398 self.ids.len().saturating_mul(16 * 1024)
400 }
401
402 fn anonymized(&self) -> AnonymizedRequest {
403 AnonymizedRequest::Direct
404 }
405}
406
407impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
408 fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
409 let mut req = Self::new();
410 for i in iter {
411 req.push(i);
412 }
413 req
414 }
415}
416
417#[derive(Debug, Clone, Default)]
419pub struct MicrodescRequest {
420 digests: Vec<MdDigest>,
422}
423
424impl MicrodescRequest {
425 pub fn new() -> Self {
427 MicrodescRequest::default()
428 }
429 pub fn push(&mut self, d: MdDigest) {
431 self.digests.push(d);
432 }
433
434 pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
436 self.digests.iter()
437 }
438}
439
440impl sealed::RequestableInner for MicrodescRequest {
441 fn make_request(&self) -> Result<http::Request<RequestBody>> {
442 let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
443 let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
444 .ok_or(RequestError::EmptyRequest)?;
445 let uri = format!("/tor/micro/d/{}", ids);
446 let req = http::Request::builder().method("GET").uri(uri);
447
448 let req = add_common_headers(req, self.anonymized());
449
450 Ok(req.body(RequestBody::default())?)
451 }
452
453 fn partial_response_body_ok(&self) -> bool {
454 self.digests.len() > 1
455 }
456
457 fn max_response_len(&self) -> usize {
458 self.digests.len().saturating_mul(8 * 1024)
460 }
461
462 fn anonymized(&self) -> AnonymizedRequest {
463 AnonymizedRequest::Direct
464 }
465}
466
467impl FromIterator<MdDigest> for MicrodescRequest {
468 fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
469 let mut req = Self::new();
470 for i in iter {
471 req.push(i);
472 }
473 req
474 }
475}
476
477#[derive(Debug, Clone)]
479#[cfg(feature = "routerdesc")]
480pub struct RouterDescRequest {
481 requested_descriptors: RequestedDescs,
483}
484
485#[derive(Debug, Clone)]
487#[cfg(feature = "routerdesc")]
488enum RequestedDescs {
489 All,
491 Digests(Vec<RdDigest>),
493}
494
495#[cfg(feature = "routerdesc")]
496impl Default for RouterDescRequest {
498 fn default() -> Self {
499 RouterDescRequest {
500 requested_descriptors: RequestedDescs::Digests(Vec::new()),
501 }
502 }
503}
504
505#[cfg(feature = "routerdesc")]
506impl RouterDescRequest {
507 pub fn all() -> Self {
509 RouterDescRequest {
510 requested_descriptors: RequestedDescs::All,
511 }
512 }
513 pub fn new() -> Self {
515 RouterDescRequest::default()
516 }
517}
518
519#[cfg(feature = "routerdesc")]
520impl sealed::RequestableInner for RouterDescRequest {
521 fn make_request(&self) -> Result<http::Request<RequestBody>> {
522 let mut uri = "/tor/server/".to_string();
523
524 match self.requested_descriptors {
525 RequestedDescs::Digests(ref digests) => {
526 uri.push_str("d/");
527 let ids = digest_list_stringify(digests, hex::encode, "+")
528 .ok_or(RequestError::EmptyRequest)?;
529 uri.push_str(&ids);
530 }
531 RequestedDescs::All => {
532 uri.push_str("all");
533 }
534 }
535
536 let req = http::Request::builder().method("GET").uri(uri);
537 let req = add_common_headers(req, self.anonymized());
538
539 Ok(req.body(RequestBody::default())?)
540 }
541
542 fn partial_response_body_ok(&self) -> bool {
543 match self.requested_descriptors {
544 RequestedDescs::Digests(ref digests) => digests.len() > 1,
545 RequestedDescs::All => true,
546 }
547 }
548
549 fn max_response_len(&self) -> usize {
550 match self.requested_descriptors {
552 RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
553 RequestedDescs::All => 64 * 1024 * 1024, }
555 }
556
557 fn anonymized(&self) -> AnonymizedRequest {
558 AnonymizedRequest::Direct
559 }
560}
561
562#[cfg(feature = "routerdesc")]
563impl FromIterator<RdDigest> for RouterDescRequest {
564 fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
565 let digests = iter.into_iter().collect();
566
567 RouterDescRequest {
568 requested_descriptors: RequestedDescs::Digests(digests),
569 }
570 }
571}
572
573#[derive(Debug, Clone, Default)]
575#[cfg(feature = "routerdesc")]
576#[non_exhaustive]
577pub struct RoutersOwnDescRequest {}
578
579#[cfg(feature = "routerdesc")]
580impl RoutersOwnDescRequest {
581 pub fn new() -> Self {
583 RoutersOwnDescRequest::default()
584 }
585}
586
587#[cfg(feature = "routerdesc")]
588impl sealed::RequestableInner for RoutersOwnDescRequest {
589 fn make_request(&self) -> Result<http::Request<RequestBody>> {
590 let uri = "/tor/server/authority";
591 let req = http::Request::builder().method("GET").uri(uri);
592 let req = add_common_headers(req, self.anonymized());
593
594 Ok(req.body(RequestBody::default())?)
595 }
596
597 fn partial_response_body_ok(&self) -> bool {
598 false
599 }
600
601 fn max_response_len(&self) -> usize {
602 128 * 1024
605 }
606
607 fn anonymized(&self) -> AnonymizedRequest {
608 AnonymizedRequest::Direct
609 }
610}
611
612#[derive(Debug, Clone)]
617#[cfg(feature = "routerdesc")]
618pub struct ExtraInfoRequest {
619 requested_extra_infos: RequestedExtraInfos,
621}
622
623#[derive(Debug, Clone)]
627#[cfg(feature = "routerdesc")]
628#[non_exhaustive]
629enum RequestedExtraInfos {
630 All,
634 Digests(Vec<ExtraInfoDigest>),
638}
639
640#[cfg(feature = "routerdesc")]
641impl Default for ExtraInfoRequest {
642 fn default() -> Self {
644 Self {
645 requested_extra_infos: RequestedExtraInfos::Digests(Vec::new()),
646 }
647 }
648}
649
650#[cfg(feature = "routerdesc")]
651impl ExtraInfoRequest {
652 pub fn all() -> Self {
654 Self {
655 requested_extra_infos: RequestedExtraInfos::All,
656 }
657 }
658 pub fn new() -> Self {
660 Self::default()
661 }
662}
663
664#[cfg(feature = "routerdesc")]
665impl sealed::RequestableInner for ExtraInfoRequest {
666 fn make_request(&self) -> Result<http::Request<RequestBody>> {
667 let mut uri = "/tor/extra/".to_string();
668
669 match &self.requested_extra_infos {
670 RequestedExtraInfos::All => uri.push_str("all"),
671 RequestedExtraInfos::Digests(digests) => {
672 uri.push_str("d/");
673 let ids = digest_list_stringify(digests, hex::encode_upper, "+")
674 .ok_or(RequestError::EmptyRequest)?;
675 uri.push_str(&ids);
676 }
677 }
678
679 let req = http::Request::builder().method("GET").uri(uri);
680 let req = add_common_headers(req, self.anonymized());
681 Ok(req.body(RequestBody::default())?)
682 }
683
684 fn partial_response_body_ok(&self) -> bool {
685 match &self.requested_extra_infos {
686 RequestedExtraInfos::Digests(digests) => digests.len() > 1,
687 RequestedExtraInfos::All => true,
688 }
689 }
690
691 fn max_response_len(&self) -> usize {
692 match &self.requested_extra_infos {
695 RequestedExtraInfos::Digests(digests) => digests.len().saturating_mul(16 * 1024),
696 RequestedExtraInfos::All => 128 * 1024 * 1024,
697 }
698 }
699
700 fn anonymized(&self) -> AnonymizedRequest {
701 AnonymizedRequest::Direct
702 }
703}
704
705#[cfg(feature = "routerdesc")]
706impl FromIterator<ExtraInfoDigest> for ExtraInfoRequest {
707 fn from_iter<T: IntoIterator<Item = ExtraInfoDigest>>(iter: T) -> Self {
708 Self {
709 requested_extra_infos: RequestedExtraInfos::Digests(iter.into_iter().collect()),
710 }
711 }
712}
713
714#[derive(Debug, Clone)]
718#[cfg(feature = "hs-client")]
719pub struct HsDescDownloadRequest {
720 hsid: HsBlindId,
722 max_len: usize,
724}
725
726#[cfg(feature = "hs-client")]
727impl HsDescDownloadRequest {
728 pub fn new(hsid: HsBlindId) -> Self {
731 const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
733 HsDescDownloadRequest {
734 hsid,
735 max_len: DEFAULT_HSDESC_MAX_LEN,
736 }
737 }
738
739 pub fn set_max_len(&mut self, max_len: usize) {
741 self.max_len = max_len;
742 }
743}
744
745#[cfg(feature = "hs-client")]
746impl sealed::RequestableInner for HsDescDownloadRequest {
747 fn make_request(&self) -> Result<http::Request<RequestBody>> {
748 let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
749 let uri = format!("/tor/hs/3/{}", hsid);
752 let req = http::Request::builder().method("GET").uri(uri);
753 let req = add_common_headers(req, self.anonymized());
754 Ok(req.body(RequestBody::default())?)
755 }
756
757 fn partial_response_body_ok(&self) -> bool {
758 false
759 }
760
761 fn max_response_len(&self) -> usize {
762 self.max_len
763 }
764
765 fn anonymized(&self) -> AnonymizedRequest {
766 AnonymizedRequest::Anonymized
767 }
768}
769
770#[allow(unused)]
772macro_rules! upload_request {
773 {
774 $(
775 $(#[$m:meta])*
776 pub struct $t:ident (
777 $anonymity:ident,
780 $uri:expr,
783 $max_response_len:expr
790 )
791 );*
792 $(;)?
793 } => {
794 $(
795 $(#[$m])*
796 #[derive(Clone, Debug)]
797 pub struct $t(Arc<str>);
798
799 impl $t {
800 pub fn new(document: Arc<str>) -> Self {
802 Self(document)
803 }
804 }
805
806 impl sealed::RequestableInner for $t {
807 fn make_request(&self) -> Result<http::Request<RequestBody>> {
808 const URI: &str = $uri;
810
811 let req = http::Request::builder().method("POST").uri(URI);
812 let req = add_common_headers(req, self.anonymized());
813 Ok(req.body(RequestBody::from(Arc::clone(&self.0)))?)
814 }
815
816 fn partial_response_body_ok(&self) -> bool {
817 false
818 }
819
820 fn max_response_len(&self) -> usize {
821 $max_response_len
822 }
823
824 fn anonymized(&self) -> AnonymizedRequest {
825 AnonymizedRequest::$anonymity
826 }
827 }
828 )*
829 }
830}
831
832#[cfg(feature = "hs-service")]
833upload_request! {
834
835 pub struct HsDescUploadRequest(
839 Anonymized,
840 "/tor/hs/3/publish",
841 1024
849 )
850}
851
852#[cfg(feature = "relay")]
853upload_request! {
854 pub struct UploadRouterDesc(Direct, "/tor/", 4096);
856
857}
858
859const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
861
862fn all_encodings() -> String {
864 #[allow(unused_mut)]
865 let mut encodings = UNIVERSAL_ENCODINGS.to_string();
866 #[cfg(feature = "xz")]
867 {
868 encodings += ", x-tor-lzma";
869 }
870 #[cfg(feature = "zstd")]
871 {
872 encodings += ", x-zstd";
873 }
874
875 encodings
876}
877
878fn add_common_headers(
882 req: http::request::Builder,
883 anon: AnonymizedRequest,
884) -> http::request::Builder {
885 match anon {
887 AnonymizedRequest::Anonymized => {
888 req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
891 }
892 AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
893 }
894}
895
896#[cfg(test)]
897mod test {
898 #![allow(clippy::bool_assert_comparison)]
900 #![allow(clippy::clone_on_copy)]
901 #![allow(clippy::dbg_macro)]
902 #![allow(clippy::mixed_attributes_style)]
903 #![allow(clippy::print_stderr)]
904 #![allow(clippy::print_stdout)]
905 #![allow(clippy::single_char_pattern)]
906 #![allow(clippy::unwrap_used)]
907 #![allow(clippy::unchecked_time_subtraction)]
908 #![allow(clippy::useless_vec)]
909 #![allow(clippy::needless_pass_by_value)]
910 #![allow(clippy::string_slice)] use super::sealed::RequestableInner;
913 use super::*;
914 use web_time_compat::SystemTimeExt;
915
916 #[test]
917 fn test_md_request() -> Result<()> {
918 let d1 = b"This is a testing digest. it isn";
919 let d2 = b"'t actually SHA-256.............";
920
921 let mut req = MicrodescRequest::default();
922 req.push(*d1);
923 assert!(!req.partial_response_body_ok());
924 req.push(*d2);
925 assert!(req.partial_response_body_ok());
926 assert_eq!(req.max_response_len(), 16 << 10);
927
928 let req = crate::util::request_to_string(&req.make_request()?);
929
930 assert_eq!(
931 req,
932 format!(
933 "GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
934 all_encodings()
935 )
936 );
937
938 let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
940 let ds: Vec<_> = req2.digests().collect();
941 assert_eq!(ds, vec![d1, d2]);
942 let req2 = crate::util::request_to_string(&req2.make_request()?);
943 assert_eq!(req, req2);
944
945 Ok(())
946 }
947
948 #[test]
949 fn test_cert_request() -> Result<()> {
950 let d1 = b"This is a testing dn";
951 let d2 = b"'t actually SHA-256.";
952 let key1 = AuthCertKeyIds {
953 id_fingerprint: (*d1).into(),
954 sk_fingerprint: (*d2).into(),
955 };
956
957 let d3 = b"blah blah blah 1 2 3";
958 let d4 = b"I like pizza from Na";
959 let key2 = AuthCertKeyIds {
960 id_fingerprint: (*d3).into(),
961 sk_fingerprint: (*d4).into(),
962 };
963
964 let mut req = AuthCertRequest::default();
965 req.push(key1);
966 assert!(!req.partial_response_body_ok());
967 req.push(key2);
968 assert!(req.partial_response_body_ok());
969 assert_eq!(req.max_response_len(), 32 << 10);
970
971 let keys: Vec<_> = req.keys().collect();
972 assert_eq!(keys, vec![&key1, &key2]);
973
974 let req = crate::util::request_to_string(&req.make_request()?);
975
976 assert_eq!(
977 req,
978 format!(
979 "GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
980 all_encodings()
981 )
982 );
983
984 let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
985 let req2 = crate::util::request_to_string(&req2.make_request()?);
986 assert_eq!(req, req2);
987
988 Ok(())
989 }
990
991 #[test]
992 fn test_consensus_request() -> Result<()> {
993 let d1 = RsaIdentity::from_bytes(
994 &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
995 )
996 .unwrap();
997
998 let d2 = b"blah blah blah 12 blah blah blah";
999 let d3 = SystemTime::get();
1000 let mut req = ConsensusRequest::default();
1001
1002 let when = httpdate::fmt_http_date(d3);
1003
1004 req.push_authority_id(d1);
1005 req.push_old_consensus_digest(*d2);
1006 req.set_last_consensus_date(d3);
1007 assert!(!req.partial_response_body_ok());
1008 assert_eq!(req.max_response_len(), (16 << 20) - 1);
1009 assert_eq!(req.old_consensus_digests().next(), Some(d2));
1010 assert_eq!(req.authority_ids().next(), Some(&d1));
1011 assert_eq!(req.last_consensus_date(), Some(d3));
1012
1013 let req = crate::util::request_to_string(&req.make_request()?);
1014
1015 assert_eq!(
1016 req,
1017 format!(
1018 "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",
1019 all_encodings(),
1020 when
1021 )
1022 );
1023
1024 let req = ConsensusRequest::default();
1026 let req = crate::util::request_to_string(&req.make_request()?);
1027 assert_eq!(
1028 req,
1029 format!(
1030 "GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1031 all_encodings()
1032 )
1033 );
1034
1035 Ok(())
1036 }
1037
1038 #[test]
1039 #[cfg(feature = "routerdesc")]
1040 fn test_rd_request_all() -> Result<()> {
1041 let req = RouterDescRequest::all();
1042 assert!(req.partial_response_body_ok());
1043 assert_eq!(req.max_response_len(), 1 << 26);
1044
1045 let req = crate::util::request_to_string(&req.make_request()?);
1046
1047 assert_eq!(
1048 req,
1049 format!(
1050 "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1051 all_encodings()
1052 )
1053 );
1054
1055 Ok(())
1056 }
1057
1058 #[test]
1059 #[cfg(feature = "routerdesc")]
1060 fn test_rd_request() -> Result<()> {
1061 let d1 = b"at some point I got ";
1062 let d2 = b"of writing in hex...";
1063
1064 let mut req = RouterDescRequest::default();
1065
1066 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
1067 digests.push(*d1);
1068 }
1069 assert!(!req.partial_response_body_ok());
1070 if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
1071 digests.push(*d2);
1072 }
1073 assert!(req.partial_response_body_ok());
1074 assert_eq!(req.max_response_len(), 16 << 10);
1075
1076 let req = crate::util::request_to_string(&req.make_request()?);
1077
1078 assert_eq!(
1079 req,
1080 format!(
1081 "GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1082 all_encodings()
1083 )
1084 );
1085
1086 let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
1088 let ds: Vec<_> = match req2.requested_descriptors {
1089 RequestedDescs::Digests(ref digests) => digests.iter().collect(),
1090 RequestedDescs::All => Vec::new(),
1091 };
1092 assert_eq!(ds, vec![d1, d2]);
1093 let req2 = crate::util::request_to_string(&req2.make_request()?);
1094 assert_eq!(req, req2);
1095 Ok(())
1096 }
1097
1098 #[test]
1099 #[cfg(feature = "routerdesc")]
1100 fn test_extra_info_request() -> Result<()> {
1101 let req = ExtraInfoRequest::from_iter([[0; 20], [1; 20], [2; 20]]);
1102 assert_eq!(
1103 crate::util::request_to_string(&req.make_request()?),
1104 format!(
1105 "GET /tor/extra/d/{}+{}+{} HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1106 hex::encode_upper([0; 20]),
1107 hex::encode_upper([1; 20]),
1108 hex::encode_upper([2; 20]),
1109 all_encodings()
1110 )
1111 );
1112
1113 let req = ExtraInfoRequest::all();
1114 assert_eq!(
1115 crate::util::request_to_string(&req.make_request()?),
1116 format!(
1117 "GET /tor/extra/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1118 all_encodings()
1119 )
1120 );
1121 Ok(())
1122 }
1123
1124 #[test]
1125 #[cfg(feature = "hs-client")]
1126 fn test_hs_desc_download_request() -> Result<()> {
1127 use tor_llcrypto::pk::ed25519::Ed25519Identity;
1128 let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
1129 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
1130 let hsid = HsBlindId::from(hsid);
1131 let req = HsDescDownloadRequest::new(hsid);
1132 assert!(!req.partial_response_body_ok());
1133 assert_eq!(req.max_response_len(), 50 * 1000);
1134
1135 let req = crate::util::request_to_string(&req.make_request()?);
1136
1137 assert_eq!(
1138 req,
1139 format!(
1140 "GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
1141 UNIVERSAL_ENCODINGS
1142 )
1143 );
1144
1145 Ok(())
1146 }
1147}