tor_dirclient/
request.rs

1//! Descriptions objects for different kinds of directory requests
2//! that we can make.
3
4use 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
15/// Alias for a result with a `RequestError`.
16type 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
30/// Declare an inaccessible public type.
31pub(crate) mod sealed {
32    use super::{AnonymizedRequest, ClientCirc, Result};
33
34    use std::future::Future;
35    use std::pin::Pin;
36
37    /// Sealed trait to help implement [`Requestable`](super::Requestable): not
38    /// visible outside this crate, so we can change its methods however we like.
39    pub trait RequestableInner: Send + Sync {
40        /// Build an [`http::Request`] from this Requestable, if
41        /// it is well-formed.
42        //
43        // TODO: This API is a bit troublesome in how it takes &self and
44        // returns a Request<String>.  First, most Requestables don't actually have
45        // a body to send, and for them having an empty String in their body is a
46        // bit silly.  Second, taking a reference to self but returning an owned
47        // String means that we will often have to clone an internal string owned by
48        // this Requestable instance.
49        fn make_request(&self) -> Result<http::Request<String>>;
50
51        /// Return true if partial response bodies are potentially useful.
52        ///
53        /// This is true for request types where we're going to be downloading
54        /// multiple documents, and we know how to parse out the ones we wanted
55        /// if the answer is truncated.
56        fn partial_response_body_ok(&self) -> bool;
57
58        /// Return the maximum allowable response length we'll accept for this
59        /// request.
60        fn max_response_len(&self) -> usize {
61            (16 * 1024 * 1024) - 1
62        }
63
64        /// Return an error if there is some problem with the provided circuit that
65        /// would keep it from being used for this request.
66        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        /// Return a value to say whether this request must be anonymized.
75        fn anonymized(&self) -> AnonymizedRequest;
76    }
77}
78
79/// A request for an object that can be served over the Tor directory system.
80pub trait Requestable: sealed::RequestableInner {
81    /// Return a wrapper around this [`Requestable`] that implements `Debug`,
82    /// and whose output shows the actual HTTP request that will be generated.
83    ///
84    /// The format is not guaranteed to  be stable.
85    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
94/// A wrapper to implement [`Requestable::debug_request`].
95pub 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/// How much clock skew do we allow in the distance between the directory
104/// cache's clock and our own?
105///
106///  If we find more skew than this, we end the
107/// request early, on the theory that the directory will not tell us any
108/// information we'd accept.
109#[derive(Clone, Debug)]
110struct SkewLimit {
111    /// We refuse to proceed if the directory says we are more fast than this.
112    ///
113    /// (This is equivalent to deciding that, from our perspective, the
114    /// directory is at least this slow.)
115    max_fast: Duration,
116
117    /// We refuse to proceed if the directory says that we are more slow than
118    /// this.
119    ///
120    /// (This is equivalent to deciding that, from our perspective, the
121    /// directory is at least this fast.)
122    max_slow: Duration,
123}
124
125/// A Request for a consensus directory.
126#[derive(Debug, Clone)]
127pub struct ConsensusRequest {
128    /// What flavor of consensus are we asking for?  Right now, only
129    /// "microdesc" and "ns" are supported.
130    flavor: ConsensusFlavor,
131    /// A list of the authority identities that we believe in.  We tell the
132    /// directory cache only to give us a consensus if it is signed by enough
133    /// of these authorities.
134    authority_ids: Vec<RsaIdentity>,
135    /// The publication time of the most recent consensus we have.  Used to
136    /// generate an If-Modified-Since header so that we don't get a document
137    /// we already have.
138    last_consensus_published: Option<SystemTime>,
139    /// A set of SHA3-256 digests of the _signed portion_ of consensuses we have.
140    /// Used to declare what diffs we would accept.
141    ///
142    /// (Currently we don't send this, since we can't handle diffs.)
143    last_consensus_sha3_256: Vec<[u8; 32]>,
144    /// If present, the largest amount of clock skew to allow between ourself and a directory cache.
145    skew_limit: Option<SkewLimit>,
146}
147
148impl ConsensusRequest {
149    /// Create a new request for a consensus directory document.
150    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    /// Add `id` to the list of authorities that this request should
161    /// say we believe in.
162    pub fn push_authority_id(&mut self, id: RsaIdentity) {
163        self.authority_ids.push(id);
164    }
165
166    /// Add `d` to the list of consensus digests this request should
167    /// say we already have.
168    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
169        self.last_consensus_sha3_256.push(d);
170    }
171
172    /// Set the publication time we should say we have for our last
173    /// consensus to `when`.
174    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
175        self.last_consensus_published = Some(when);
176    }
177
178    /// Return a slice of the consensus digests that we're saying we
179    /// already have.
180    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
181        self.last_consensus_sha3_256.iter()
182    }
183
184    /// Return an iterator of the authority identities that this request
185    /// is saying we believe in.
186    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
187        self.authority_ids.iter()
188    }
189
190    /// Return the date we're reporting for our most recent consensus.
191    pub fn last_consensus_date(&self) -> Option<SystemTime> {
192        self.last_consensus_published
193    }
194
195    /// Tell the directory client that we should abort the request early if the
196    /// directory's clock skew exceeds certain limits.
197    ///
198    /// The `max_fast` parameter is the most fast that we're willing to be with
199    /// respect to the directory (or in other words, the most slow that we're
200    /// willing to let the directory be with respect to us).
201    ///
202    /// The `max_slow` parameter is the most _slow_ that we're willing to be with
203    /// respect to the directory ((or in other words, the most slow that we're
204    /// willing to let the directory be with respect to us).
205    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
210/// Convert a list of digests in some format to a string, for use in a request
211///
212/// The digests `DL` will be sorted, converted to strings with `EF`,
213/// separated with `sep`, and returned as an fresh `String`.
214///
215/// If the digests list is empty, returns None instead.
216//
217// In principle this ought to be doable with much less allocating,
218// starting with hex::encode etc.
219fn 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    // name collision with unstable Iterator::intersperse
232    // https://github.com/rust-lang/rust/issues/48919
233    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        // Build the URL.
246        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            // With authorities, "../consensus/<F1>+<F2>+<F3>.z"
257            uri.push('/');
258            uri.push_str(&ids);
259        }
260        // Without authorities, "../consensus-microdesc.z"
261        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        // Possibly, add an if-modified-since header.
267        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        // Possibly, add an X-Or-Diff-From-Consensus header.
275        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            // This is the clock skew _according to the directory_.
294            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/// A request for one or more authority certificates.
313#[derive(Debug, Clone, Default)]
314pub struct AuthCertRequest {
315    /// The identity/signing keys of the certificates we want.
316    ids: Vec<AuthCertKeyIds>,
317}
318
319impl AuthCertRequest {
320    /// Create a new request, asking for no authority certificates.
321    pub fn new() -> Self {
322        AuthCertRequest::default()
323    }
324
325    /// Add `ids` to the list of certificates we're asking for.
326    pub fn push(&mut self, ids: AuthCertKeyIds) {
327        self.ids.push(ids);
328    }
329
330    /// Return a list of the keys that we're asking for.
331    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        // TODO: Pick a more principled number; I just made this one up.
369        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/// A request for one or more microdescriptors
388#[derive(Debug, Clone, Default)]
389pub struct MicrodescRequest {
390    /// The SHA256 digests of the microdescriptors we want.
391    digests: Vec<MdDigest>,
392}
393
394impl MicrodescRequest {
395    /// Construct a request for no microdescriptors.
396    pub fn new() -> Self {
397        MicrodescRequest::default()
398    }
399    /// Add `d` to the list of microdescriptors we want to request.
400    pub fn push(&mut self, d: MdDigest) {
401        self.digests.push(d);
402    }
403
404    /// Return a list of the microdescriptor digests that we're asking for.
405    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        // TODO: Pick a more principled number; I just made this one up.
429        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/// A request for one, many or all router descriptors.
448#[derive(Debug, Clone)]
449#[cfg(feature = "routerdesc")]
450pub struct RouterDescRequest {
451    /// The descriptors to request.
452    requested_descriptors: RequestedDescs,
453}
454
455/// Tracks the different router descriptor types.
456#[derive(Debug, Clone)]
457#[cfg(feature = "routerdesc")]
458enum RequestedDescs {
459    /// If this is set, we just ask for all the descriptors.
460    AllDescriptors,
461    /// A list of digests to download.
462    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    /// Construct a request for all router descriptors.
477    pub fn all() -> Self {
478        RouterDescRequest {
479            requested_descriptors: RequestedDescs::AllDescriptors,
480        }
481    }
482    /// Construct a new empty request.
483    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        // TODO: Pick a more principled number; I just made these up.
522        match self.requested_descriptors {
523            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
524            RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
525        }
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/// A request for the descriptor of whatever relay we are making the request to
545#[derive(Debug, Clone, Default)]
546#[cfg(feature = "routerdesc")]
547#[non_exhaustive]
548pub struct RoutersOwnDescRequest {}
549
550#[cfg(feature = "routerdesc")]
551impl RoutersOwnDescRequest {
552    /// Construct a new request.
553    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/// A request to download a hidden service descriptor
578///
579/// rend-spec-v3 2.2.6
580#[derive(Debug, Clone)]
581#[cfg(feature = "hs-client")]
582pub struct HsDescDownloadRequest {
583    /// What hidden service?
584    hsid: HsBlindId,
585    /// What's the largest acceptable response length?
586    max_len: usize,
587}
588
589#[cfg(feature = "hs-client")]
590impl HsDescDownloadRequest {
591    /// Construct a request for a single onion service descriptor by its
592    /// blinded ID.
593    pub fn new(hsid: HsBlindId) -> Self {
594        /// Default maximum length to use when we have no other information.
595        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
596        HsDescDownloadRequest {
597            hsid,
598            max_len: DEFAULT_HSDESC_MAX_LEN,
599        }
600    }
601
602    /// Set the maximum acceptable response length.
603    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        // We hardcode version 3 here; if we ever have a v4 onion service
613        // descriptor, it will need a different kind of Request.
614        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/// A request to upload a hidden service descriptor
634///
635/// rend-spec-v3 2.2.6
636#[derive(Debug, Clone)]
637#[cfg(feature = "hs-service")]
638pub struct HsDescUploadRequest(String);
639
640#[cfg(feature = "hs-service")]
641impl HsDescUploadRequest {
642    /// Construct a request for uploading a single onion service descriptor.
643    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        /// The upload URI.
652        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        // We expect the response _body_ to be empty, but the max_response_len
665        // is not zero because it represents the _total_ length of the response
666        // (which includes the length of the status line and headers).
667        //
668        // A real Tor POST response will always be less than that length, which
669        // will fit into 3 DATA messages at most. (The reply will be a single
670        // HTTP line, followed by a Date header.)
671        1024
672    }
673
674    fn anonymized(&self) -> AnonymizedRequest {
675        AnonymizedRequest::Anonymized
676    }
677}
678
679/// Encodings that all Tor clients support.
680const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
681
682/// List all the encodings we accept
683fn 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
698/// Add commonly used headers to the HTTP request.
699///
700/// (Right now, this is only Accept-Encoding.)
701fn add_common_headers(
702    req: http::request::Builder,
703    anon: AnonymizedRequest,
704) -> http::request::Builder {
705    // TODO: gzip, brotli
706    match anon {
707        AnonymizedRequest::Anonymized => {
708            // In an anonymized request, we do not admit to supporting any
709            // encoding besides those that are always available.
710            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    // @@ begin test lint list maintained by maint/add_warning @@
719    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
731    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        // Try it with FromIterator, and use some accessors.
752        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        // Request without authorities
827        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        // Try it with FromIterator, and use some accessors.
879        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}