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    last_consensus_sha3_256: Vec<[u8; 32]>,
142    /// If present, the largest amount of clock skew to allow between ourself and a directory cache.
143    skew_limit: Option<SkewLimit>,
144}
145
146impl ConsensusRequest {
147    /// Create a new request for a consensus directory document.
148    pub fn new(flavor: ConsensusFlavor) -> Self {
149        ConsensusRequest {
150            flavor,
151            authority_ids: Vec::new(),
152            last_consensus_published: None,
153            last_consensus_sha3_256: Vec::new(),
154            skew_limit: None,
155        }
156    }
157
158    /// Add `id` to the list of authorities that this request should
159    /// say we believe in.
160    pub fn push_authority_id(&mut self, id: RsaIdentity) {
161        self.authority_ids.push(id);
162    }
163
164    /// Add `d` to the list of consensus digests this request should
165    /// say we already have.
166    pub fn push_old_consensus_digest(&mut self, d: [u8; 32]) {
167        self.last_consensus_sha3_256.push(d);
168    }
169
170    /// Set the publication time we should say we have for our last
171    /// consensus to `when`.
172    pub fn set_last_consensus_date(&mut self, when: SystemTime) {
173        self.last_consensus_published = Some(when);
174    }
175
176    /// Return a slice of the consensus digests that we're saying we
177    /// already have.
178    pub fn old_consensus_digests(&self) -> impl Iterator<Item = &[u8; 32]> {
179        self.last_consensus_sha3_256.iter()
180    }
181
182    /// Return an iterator of the authority identities that this request
183    /// is saying we believe in.
184    pub fn authority_ids(&self) -> impl Iterator<Item = &RsaIdentity> {
185        self.authority_ids.iter()
186    }
187
188    /// Return the date we're reporting for our most recent consensus.
189    pub fn last_consensus_date(&self) -> Option<SystemTime> {
190        self.last_consensus_published
191    }
192
193    /// Tell the directory client that we should abort the request early if the
194    /// directory's clock skew exceeds certain limits.
195    ///
196    /// The `max_fast` parameter is the most fast that we're willing to be with
197    /// respect to the directory (or in other words, the most slow that we're
198    /// willing to let the directory be with respect to us).
199    ///
200    /// The `max_slow` parameter is the most _slow_ that we're willing to be with
201    /// respect to the directory ((or in other words, the most slow that we're
202    /// willing to let the directory be with respect to us).
203    pub fn set_skew_limit(&mut self, max_fast: Duration, max_slow: Duration) {
204        self.skew_limit = Some(SkewLimit { max_fast, max_slow });
205    }
206}
207
208/// Convert a list of digests in some format to a string, for use in a request
209///
210/// The digests `DL` will be sorted, converted to strings with `EF`,
211/// separated with `sep`, and returned as an fresh `String`.
212///
213/// If the digests list is empty, returns None instead.
214//
215// In principle this ought to be doable with much less allocating,
216// starting with hex::encode etc.
217fn digest_list_stringify<'d, D, DL, EF>(digests: DL, encode: EF, sep: &str) -> Option<String>
218where
219    DL: IntoIterator<Item = &'d D> + 'd,
220    D: PartialOrd + Ord + 'd,
221    EF: Fn(&'d D) -> String,
222{
223    let mut digests = digests.into_iter().collect_vec();
224    if digests.is_empty() {
225        return None;
226    }
227    digests.sort_unstable();
228    let ids = digests.into_iter().map(encode).map(Cow::Owned);
229    // name collision with unstable Iterator::intersperse
230    // https://github.com/rust-lang/rust/issues/48919
231    let ids = Itertools::intersperse(ids, Cow::Borrowed(sep)).collect::<String>();
232    Some(ids)
233}
234
235impl Default for ConsensusRequest {
236    fn default() -> Self {
237        Self::new(ConsensusFlavor::Microdesc)
238    }
239}
240
241impl sealed::RequestableInner for ConsensusRequest {
242    fn make_request(&self) -> Result<http::Request<String>> {
243        // Build the URL.
244        let mut uri = "/tor/status-vote/current/consensus".to_string();
245        match self.flavor {
246            ConsensusFlavor::Ns => {}
247            flav => {
248                uri.push('-');
249                uri.push_str(flav.name());
250            }
251        }
252        let d_encode_hex = |id: &RsaIdentity| hex::encode(id.as_bytes());
253        if let Some(ids) = digest_list_stringify(&self.authority_ids, d_encode_hex, "+") {
254            // With authorities, "../consensus/<F1>+<F2>+<F3>"
255            uri.push('/');
256            uri.push_str(&ids);
257        }
258        // Without authorities, "../consensus-microdesc"
259
260        let mut req = http::Request::builder().method("GET").uri(uri);
261        req = add_common_headers(req, self.anonymized());
262
263        // Possibly, add an if-modified-since header.
264        if let Some(when) = self.last_consensus_date() {
265            req = req.header(
266                http::header::IF_MODIFIED_SINCE,
267                httpdate::fmt_http_date(when),
268            );
269        }
270
271        // Possibly, add an X-Or-Diff-From-Consensus header.
272        if let Some(ids) = digest_list_stringify(&self.last_consensus_sha3_256, hex::encode, ", ") {
273            req = req.header("X-Or-Diff-From-Consensus", &ids);
274        }
275
276        Ok(req.body(String::new())?)
277    }
278
279    fn partial_response_body_ok(&self) -> bool {
280        false
281    }
282
283    fn check_circuit<'a>(
284        &self,
285        circ: &'a ClientCirc,
286    ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
287        let skew_limit = self.skew_limit.clone();
288        Box::pin(async move {
289            use tor_proto::ClockSkew::*;
290            // This is the clock skew _according to the directory_.
291            let skew = circ.first_hop_clock_skew().await?;
292            match (&skew_limit, &skew) {
293                (Some(SkewLimit { max_slow, .. }), Slow(slow)) if slow > max_slow => {
294                    Err(RequestError::TooMuchClockSkew)
295                }
296                (Some(SkewLimit { max_fast, .. }), Fast(fast)) if fast > max_fast => {
297                    Err(RequestError::TooMuchClockSkew)
298                }
299                (_, _) => Ok(()),
300            }
301        })
302    }
303
304    fn anonymized(&self) -> AnonymizedRequest {
305        AnonymizedRequest::Direct
306    }
307}
308
309/// A request for one or more authority certificates.
310#[derive(Debug, Clone, Default)]
311pub struct AuthCertRequest {
312    /// The identity/signing keys of the certificates we want.
313    ids: Vec<AuthCertKeyIds>,
314}
315
316impl AuthCertRequest {
317    /// Create a new request, asking for no authority certificates.
318    pub fn new() -> Self {
319        AuthCertRequest::default()
320    }
321
322    /// Add `ids` to the list of certificates we're asking for.
323    pub fn push(&mut self, ids: AuthCertKeyIds) {
324        self.ids.push(ids);
325    }
326
327    /// Return a list of the keys that we're asking for.
328    pub fn keys(&self) -> impl Iterator<Item = &AuthCertKeyIds> {
329        self.ids.iter()
330    }
331}
332
333impl sealed::RequestableInner for AuthCertRequest {
334    fn make_request(&self) -> Result<http::Request<String>> {
335        if self.ids.is_empty() {
336            return Err(RequestError::EmptyRequest);
337        }
338        let mut ids = self.ids.clone();
339        ids.sort_unstable();
340
341        let ids: Vec<String> = ids
342            .iter()
343            .map(|id| {
344                format!(
345                    "{}-{}",
346                    hex::encode(id.id_fingerprint.as_bytes()),
347                    hex::encode(id.sk_fingerprint.as_bytes())
348                )
349            })
350            .collect();
351
352        let uri = format!("/tor/keys/fp-sk/{}", &ids.join("+"));
353
354        let req = http::Request::builder().method("GET").uri(uri);
355        let req = add_common_headers(req, self.anonymized());
356
357        Ok(req.body(String::new())?)
358    }
359
360    fn partial_response_body_ok(&self) -> bool {
361        self.ids.len() > 1
362    }
363
364    fn max_response_len(&self) -> usize {
365        // TODO: Pick a more principled number; I just made this one up.
366        self.ids.len().saturating_mul(16 * 1024)
367    }
368
369    fn anonymized(&self) -> AnonymizedRequest {
370        AnonymizedRequest::Direct
371    }
372}
373
374impl FromIterator<AuthCertKeyIds> for AuthCertRequest {
375    fn from_iter<I: IntoIterator<Item = AuthCertKeyIds>>(iter: I) -> Self {
376        let mut req = Self::new();
377        for i in iter {
378            req.push(i);
379        }
380        req
381    }
382}
383
384/// A request for one or more microdescriptors
385#[derive(Debug, Clone, Default)]
386pub struct MicrodescRequest {
387    /// The SHA256 digests of the microdescriptors we want.
388    digests: Vec<MdDigest>,
389}
390
391impl MicrodescRequest {
392    /// Construct a request for no microdescriptors.
393    pub fn new() -> Self {
394        MicrodescRequest::default()
395    }
396    /// Add `d` to the list of microdescriptors we want to request.
397    pub fn push(&mut self, d: MdDigest) {
398        self.digests.push(d);
399    }
400
401    /// Return a list of the microdescriptor digests that we're asking for.
402    pub fn digests(&self) -> impl Iterator<Item = &MdDigest> {
403        self.digests.iter()
404    }
405}
406
407impl sealed::RequestableInner for MicrodescRequest {
408    fn make_request(&self) -> Result<http::Request<String>> {
409        let d_encode_b64 = |d: &[u8; 32]| Base64Unpadded::encode_string(&d[..]);
410        let ids = digest_list_stringify(&self.digests, d_encode_b64, "-")
411            .ok_or(RequestError::EmptyRequest)?;
412        let uri = format!("/tor/micro/d/{}", &ids);
413        let req = http::Request::builder().method("GET").uri(uri);
414
415        let req = add_common_headers(req, self.anonymized());
416
417        Ok(req.body(String::new())?)
418    }
419
420    fn partial_response_body_ok(&self) -> bool {
421        self.digests.len() > 1
422    }
423
424    fn max_response_len(&self) -> usize {
425        // TODO: Pick a more principled number; I just made this one up.
426        self.digests.len().saturating_mul(8 * 1024)
427    }
428
429    fn anonymized(&self) -> AnonymizedRequest {
430        AnonymizedRequest::Direct
431    }
432}
433
434impl FromIterator<MdDigest> for MicrodescRequest {
435    fn from_iter<I: IntoIterator<Item = MdDigest>>(iter: I) -> Self {
436        let mut req = Self::new();
437        for i in iter {
438            req.push(i);
439        }
440        req
441    }
442}
443
444/// A request for one, many or all router descriptors.
445#[derive(Debug, Clone)]
446#[cfg(feature = "routerdesc")]
447pub struct RouterDescRequest {
448    /// The descriptors to request.
449    requested_descriptors: RequestedDescs,
450}
451
452/// Tracks the different router descriptor types.
453#[derive(Debug, Clone)]
454#[cfg(feature = "routerdesc")]
455enum RequestedDescs {
456    /// If this is set, we just ask for all the descriptors.
457    AllDescriptors,
458    /// A list of digests to download.
459    Digests(Vec<RdDigest>),
460}
461
462#[cfg(feature = "routerdesc")]
463impl Default for RouterDescRequest {
464    fn default() -> Self {
465        RouterDescRequest {
466            requested_descriptors: RequestedDescs::Digests(Vec::new()),
467        }
468    }
469}
470
471#[cfg(feature = "routerdesc")]
472impl RouterDescRequest {
473    /// Construct a request for all router descriptors.
474    pub fn all() -> Self {
475        RouterDescRequest {
476            requested_descriptors: RequestedDescs::AllDescriptors,
477        }
478    }
479    /// Construct a new empty request.
480    pub fn new() -> Self {
481        RouterDescRequest::default()
482    }
483}
484
485#[cfg(feature = "routerdesc")]
486impl sealed::RequestableInner for RouterDescRequest {
487    fn make_request(&self) -> Result<http::Request<String>> {
488        let mut uri = "/tor/server/".to_string();
489
490        match self.requested_descriptors {
491            RequestedDescs::Digests(ref digests) => {
492                uri.push_str("d/");
493                let ids = digest_list_stringify(digests, hex::encode, "+")
494                    .ok_or(RequestError::EmptyRequest)?;
495                uri.push_str(&ids);
496            }
497            RequestedDescs::AllDescriptors => {
498                uri.push_str("all");
499            }
500        }
501
502        let req = http::Request::builder().method("GET").uri(uri);
503        let req = add_common_headers(req, self.anonymized());
504
505        Ok(req.body(String::new())?)
506    }
507
508    fn partial_response_body_ok(&self) -> bool {
509        match self.requested_descriptors {
510            RequestedDescs::Digests(ref digests) => digests.len() > 1,
511            RequestedDescs::AllDescriptors => true,
512        }
513    }
514
515    fn max_response_len(&self) -> usize {
516        // TODO: Pick a more principled number; I just made these up.
517        match self.requested_descriptors {
518            RequestedDescs::Digests(ref digests) => digests.len().saturating_mul(8 * 1024),
519            RequestedDescs::AllDescriptors => 64 * 1024 * 1024, // big but not impossible
520        }
521    }
522
523    fn anonymized(&self) -> AnonymizedRequest {
524        AnonymizedRequest::Direct
525    }
526}
527
528#[cfg(feature = "routerdesc")]
529impl FromIterator<RdDigest> for RouterDescRequest {
530    fn from_iter<I: IntoIterator<Item = RdDigest>>(iter: I) -> Self {
531        let digests = iter.into_iter().collect();
532
533        RouterDescRequest {
534            requested_descriptors: RequestedDescs::Digests(digests),
535        }
536    }
537}
538
539/// A request for the descriptor of whatever relay we are making the request to
540#[derive(Debug, Clone, Default)]
541#[cfg(feature = "routerdesc")]
542#[non_exhaustive]
543pub struct RoutersOwnDescRequest {}
544
545#[cfg(feature = "routerdesc")]
546impl RoutersOwnDescRequest {
547    /// Construct a new request.
548    pub fn new() -> Self {
549        RoutersOwnDescRequest::default()
550    }
551}
552
553#[cfg(feature = "routerdesc")]
554impl sealed::RequestableInner for RoutersOwnDescRequest {
555    fn make_request(&self) -> Result<http::Request<String>> {
556        let uri = "/tor/server/authority";
557        let req = http::Request::builder().method("GET").uri(uri);
558        let req = add_common_headers(req, self.anonymized());
559
560        Ok(req.body(String::new())?)
561    }
562
563    fn partial_response_body_ok(&self) -> bool {
564        false
565    }
566
567    fn anonymized(&self) -> AnonymizedRequest {
568        AnonymizedRequest::Direct
569    }
570}
571
572/// A request to download a hidden service descriptor
573///
574/// rend-spec-v3 2.2.6
575#[derive(Debug, Clone)]
576#[cfg(feature = "hs-client")]
577pub struct HsDescDownloadRequest {
578    /// What hidden service?
579    hsid: HsBlindId,
580    /// What's the largest acceptable response length?
581    max_len: usize,
582}
583
584#[cfg(feature = "hs-client")]
585impl HsDescDownloadRequest {
586    /// Construct a request for a single onion service descriptor by its
587    /// blinded ID.
588    pub fn new(hsid: HsBlindId) -> Self {
589        /// Default maximum length to use when we have no other information.
590        const DEFAULT_HSDESC_MAX_LEN: usize = 50_000;
591        HsDescDownloadRequest {
592            hsid,
593            max_len: DEFAULT_HSDESC_MAX_LEN,
594        }
595    }
596
597    /// Set the maximum acceptable response length.
598    pub fn set_max_len(&mut self, max_len: usize) {
599        self.max_len = max_len;
600    }
601}
602
603#[cfg(feature = "hs-client")]
604impl sealed::RequestableInner for HsDescDownloadRequest {
605    fn make_request(&self) -> Result<http::Request<String>> {
606        let hsid = Base64Unpadded::encode_string(self.hsid.as_ref());
607        // We hardcode version 3 here; if we ever have a v4 onion service
608        // descriptor, it will need a different kind of Request.
609        let uri = format!("/tor/hs/3/{}", hsid);
610        let req = http::Request::builder().method("GET").uri(uri);
611        let req = add_common_headers(req, self.anonymized());
612        Ok(req.body(String::new())?)
613    }
614
615    fn partial_response_body_ok(&self) -> bool {
616        false
617    }
618
619    fn max_response_len(&self) -> usize {
620        self.max_len
621    }
622
623    fn anonymized(&self) -> AnonymizedRequest {
624        AnonymizedRequest::Anonymized
625    }
626}
627
628/// A request to upload a hidden service descriptor
629///
630/// rend-spec-v3 2.2.6
631#[derive(Debug, Clone)]
632#[cfg(feature = "hs-service")]
633pub struct HsDescUploadRequest(String);
634
635#[cfg(feature = "hs-service")]
636impl HsDescUploadRequest {
637    /// Construct a request for uploading a single onion service descriptor.
638    pub fn new(hsdesc: String) -> Self {
639        HsDescUploadRequest(hsdesc)
640    }
641}
642
643#[cfg(feature = "hs-service")]
644impl sealed::RequestableInner for HsDescUploadRequest {
645    fn make_request(&self) -> Result<http::Request<String>> {
646        /// The upload URI.
647        const URI: &str = "/tor/hs/3/publish";
648
649        let req = http::Request::builder().method("POST").uri(URI);
650        let req = add_common_headers(req, self.anonymized());
651        Ok(req.body(self.0.clone())?)
652    }
653
654    fn partial_response_body_ok(&self) -> bool {
655        false
656    }
657
658    fn max_response_len(&self) -> usize {
659        // We expect the response _body_ to be empty, but the max_response_len
660        // is not zero because it represents the _total_ length of the response
661        // (which includes the length of the status line and headers).
662        //
663        // A real Tor POST response will always be less than that length, which
664        // will fit into 3 DATA messages at most. (The reply will be a single
665        // HTTP line, followed by a Date header.)
666        1024
667    }
668
669    fn anonymized(&self) -> AnonymizedRequest {
670        AnonymizedRequest::Anonymized
671    }
672}
673
674/// Encodings that all Tor clients support.
675const UNIVERSAL_ENCODINGS: &str = "deflate, identity";
676
677/// List all the encodings we accept
678fn all_encodings() -> String {
679    #[allow(unused_mut)]
680    let mut encodings = UNIVERSAL_ENCODINGS.to_string();
681    #[cfg(feature = "xz")]
682    {
683        encodings += ", x-tor-lzma";
684    }
685    #[cfg(feature = "zstd")]
686    {
687        encodings += ", x-zstd";
688    }
689
690    encodings
691}
692
693/// Add commonly used headers to the HTTP request.
694///
695/// (Right now, this is only Accept-Encoding.)
696fn add_common_headers(
697    req: http::request::Builder,
698    anon: AnonymizedRequest,
699) -> http::request::Builder {
700    // TODO: gzip, brotli
701    match anon {
702        AnonymizedRequest::Anonymized => {
703            // In an anonymized request, we do not admit to supporting any
704            // encoding besides those that are always available.
705            req.header(http::header::ACCEPT_ENCODING, UNIVERSAL_ENCODINGS)
706        }
707        AnonymizedRequest::Direct => req.header(http::header::ACCEPT_ENCODING, all_encodings()),
708    }
709}
710
711#[cfg(test)]
712mod test {
713    // @@ begin test lint list maintained by maint/add_warning @@
714    #![allow(clippy::bool_assert_comparison)]
715    #![allow(clippy::clone_on_copy)]
716    #![allow(clippy::dbg_macro)]
717    #![allow(clippy::mixed_attributes_style)]
718    #![allow(clippy::print_stderr)]
719    #![allow(clippy::print_stdout)]
720    #![allow(clippy::single_char_pattern)]
721    #![allow(clippy::unwrap_used)]
722    #![allow(clippy::unchecked_duration_subtraction)]
723    #![allow(clippy::useless_vec)]
724    #![allow(clippy::needless_pass_by_value)]
725    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
726    use super::sealed::RequestableInner;
727    use super::*;
728
729    #[test]
730    fn test_md_request() -> Result<()> {
731        let d1 = b"This is a testing digest. it isn";
732        let d2 = b"'t actually SHA-256.............";
733
734        let mut req = MicrodescRequest::default();
735        req.push(*d1);
736        assert!(!req.partial_response_body_ok());
737        req.push(*d2);
738        assert!(req.partial_response_body_ok());
739        assert_eq!(req.max_response_len(), 16 << 10);
740
741        let req = crate::util::encode_request(&req.make_request()?);
742
743        assert_eq!(req,
744                   format!("GET /tor/micro/d/J3QgYWN0dWFsbHkgU0hBLTI1Ni4uLi4uLi4uLi4uLi4-VGhpcyBpcyBhIHRlc3RpbmcgZGlnZXN0LiBpdCBpc24 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
745
746        // Try it with FromIterator, and use some accessors.
747        let req2: MicrodescRequest = vec![*d1, *d2].into_iter().collect();
748        let ds: Vec<_> = req2.digests().collect();
749        assert_eq!(ds, vec![d1, d2]);
750        let req2 = crate::util::encode_request(&req2.make_request()?);
751        assert_eq!(req, req2);
752
753        Ok(())
754    }
755
756    #[test]
757    fn test_cert_request() -> Result<()> {
758        let d1 = b"This is a testing dn";
759        let d2 = b"'t actually SHA-256.";
760        let key1 = AuthCertKeyIds {
761            id_fingerprint: (*d1).into(),
762            sk_fingerprint: (*d2).into(),
763        };
764
765        let d3 = b"blah blah blah 1 2 3";
766        let d4 = b"I like pizza from Na";
767        let key2 = AuthCertKeyIds {
768            id_fingerprint: (*d3).into(),
769            sk_fingerprint: (*d4).into(),
770        };
771
772        let mut req = AuthCertRequest::default();
773        req.push(key1);
774        assert!(!req.partial_response_body_ok());
775        req.push(key2);
776        assert!(req.partial_response_body_ok());
777        assert_eq!(req.max_response_len(), 32 << 10);
778
779        let keys: Vec<_> = req.keys().collect();
780        assert_eq!(keys, vec![&key1, &key2]);
781
782        let req = crate::util::encode_request(&req.make_request()?);
783
784        assert_eq!(req,
785                   format!("GET /tor/keys/fp-sk/5468697320697320612074657374696e6720646e-27742061637475616c6c79205348412d3235362e+626c616820626c616820626c6168203120322033-49206c696b652070697a7a612066726f6d204e61 HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
786
787        let req2: AuthCertRequest = vec![key1, key2].into_iter().collect();
788        let req2 = crate::util::encode_request(&req2.make_request()?);
789        assert_eq!(req, req2);
790
791        Ok(())
792    }
793
794    #[test]
795    fn test_consensus_request() -> Result<()> {
796        let d1 = RsaIdentity::from_bytes(
797            &hex::decode("03479E93EBF3FF2C58C1C9DBF2DE9DE9C2801B3E").unwrap(),
798        )
799        .unwrap();
800
801        let d2 = b"blah blah blah 12 blah blah blah";
802        let d3 = SystemTime::now();
803        let mut req = ConsensusRequest::default();
804
805        let when = httpdate::fmt_http_date(d3);
806
807        req.push_authority_id(d1);
808        req.push_old_consensus_digest(*d2);
809        req.set_last_consensus_date(d3);
810        assert!(!req.partial_response_body_ok());
811        assert_eq!(req.max_response_len(), (16 << 20) - 1);
812        assert_eq!(req.old_consensus_digests().next(), Some(d2));
813        assert_eq!(req.authority_ids().next(), Some(&d1));
814        assert_eq!(req.last_consensus_date(), Some(d3));
815
816        let req = crate::util::encode_request(&req.make_request()?);
817
818        assert_eq!(req,
819                   format!("GET /tor/status-vote/current/consensus-microdesc/03479e93ebf3ff2c58c1c9dbf2de9de9c2801b3e HTTP/1.0\r\naccept-encoding: {}\r\nif-modified-since: {}\r\nx-or-diff-from-consensus: 626c616820626c616820626c616820313220626c616820626c616820626c6168\r\n\r\n", all_encodings(), when));
820
821        // Request without authorities
822        let req = ConsensusRequest::default();
823        let req = crate::util::encode_request(&req.make_request()?);
824        assert_eq!(req,
825                   format!("GET /tor/status-vote/current/consensus-microdesc HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
826
827        Ok(())
828    }
829
830    #[test]
831    #[cfg(feature = "routerdesc")]
832    fn test_rd_request_all() -> Result<()> {
833        let req = RouterDescRequest::all();
834        assert!(req.partial_response_body_ok());
835        assert_eq!(req.max_response_len(), 1 << 26);
836
837        let req = crate::util::encode_request(&req.make_request()?);
838
839        assert_eq!(
840            req,
841            format!(
842                "GET /tor/server/all HTTP/1.0\r\naccept-encoding: {}\r\n\r\n",
843                all_encodings()
844            )
845        );
846
847        Ok(())
848    }
849
850    #[test]
851    #[cfg(feature = "routerdesc")]
852    fn test_rd_request() -> Result<()> {
853        let d1 = b"at some point I got ";
854        let d2 = b"of writing in hex...";
855
856        let mut req = RouterDescRequest::default();
857
858        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
859            digests.push(*d1);
860        }
861        assert!(!req.partial_response_body_ok());
862        if let RequestedDescs::Digests(ref mut digests) = req.requested_descriptors {
863            digests.push(*d2);
864        }
865        assert!(req.partial_response_body_ok());
866        assert_eq!(req.max_response_len(), 16 << 10);
867
868        let req = crate::util::encode_request(&req.make_request()?);
869
870        assert_eq!(req,
871                   format!("GET /tor/server/d/617420736f6d6520706f696e74204920676f7420+6f662077726974696e6720696e206865782e2e2e HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", all_encodings()));
872
873        // Try it with FromIterator, and use some accessors.
874        let req2: RouterDescRequest = vec![*d1, *d2].into_iter().collect();
875        let ds: Vec<_> = match req2.requested_descriptors {
876            RequestedDescs::Digests(ref digests) => digests.iter().collect(),
877            RequestedDescs::AllDescriptors => Vec::new(),
878        };
879        assert_eq!(ds, vec![d1, d2]);
880        let req2 = crate::util::encode_request(&req2.make_request()?);
881        assert_eq!(req, req2);
882        Ok(())
883    }
884
885    #[test]
886    #[cfg(feature = "hs-client")]
887    fn test_hs_desc_download_request() -> Result<()> {
888        use tor_llcrypto::pk::ed25519::Ed25519Identity;
889        let hsid = [1, 2, 3, 4].iter().cycle().take(32).cloned().collect_vec();
890        let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
891        let hsid = HsBlindId::from(hsid);
892        let req = HsDescDownloadRequest::new(hsid);
893        assert!(!req.partial_response_body_ok());
894        assert_eq!(req.max_response_len(), 50 * 1000);
895
896        let req = crate::util::encode_request(&req.make_request()?);
897
898        assert_eq!(
899            req,
900            format!("GET /tor/hs/3/AQIDBAECAwQBAgMEAQIDBAECAwQBAgMEAQIDBAECAwQ HTTP/1.0\r\naccept-encoding: {}\r\n\r\n", UNIVERSAL_ENCODINGS)
901        );
902
903        Ok(())
904    }
905}