1
//! Directory Mirror Operation.
2
//!
3
//! # Specifications
4
//!
5
//! * [Directory cache operation](https://spec.torproject.org/dir-spec/directory-cache-operation.html).
6
//!
7
//! # Rationale
8
//!
9
//! This module implements the "core operation" of a directory mirror.
10
//! "Core operation" primarily refers to the logic involved in downloading
11
//! network documents from an upstream authority and inserting them into the
12
//! database.  This module notably **DOES NOT** provide any public (in the HTTP
13
//! sense) endpoints for querying documents.  This is purposely behind a different
14
//! module, so that the directory authority implementation can also make use of it.
15
//! You can think of this module as the one implementing the things unique
16
//! to directory mirrors.
17

            
18
use std::{
19
    collections::{HashSet, VecDeque},
20
    marker::PhantomData,
21
    net::SocketAddr,
22
};
23

            
24
use r2d2::Pool;
25
use r2d2_sqlite::SqliteConnectionManager;
26
use rand::Rng;
27
use rusqlite::Transaction;
28
use strum::IntoEnumIterator;
29
use tokio::net::TcpStream;
30
use tokio_util::compat::TokioAsyncReadCompatExt;
31
use tor_checkable::TimeBound;
32
use tor_dirclient::request::{AuthCertRequest, ConsensusRequest, Requestable};
33
use tor_dircommon::{authority::AuthorityContacts, config::DirTolerance};
34
use tor_error::{internal, into_internal};
35
use tor_netdoc::{
36
    doc::authcert::{AuthCertKeyIds, AuthCertUnverified},
37
    parse2::{self, NetdocParseable, NetdocParseableUnverified, ParseInput},
38
};
39
use tor_rtcompat::PreferredRuntime;
40
use tracing::{debug, warn};
41

            
42
use crate::{
43
    database::{self as db, AuthCertMeta, ConsensusMeta, ContentEncoding, Timestamp},
44
    err::{AuthorityRequestError, DatabaseError, OperationError},
45
    types::{FlavoredConsensusSignatures, FlavoredConsensusUnverified},
46
};
47

            
48
mod poc;
49

            
50
/// The various states for the [`StaticEngine`].
51
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
52
enum State {
53
    /// Loads the most recent valid (and verified) consensus from the database
54
    /// into memory.
55
    ///
56
    /// Transitions from:
57
    /// * Start, if a recent valid consensus exists in the database.
58
    /// * [`State::StoreConsensus`], if successfully finished.
59
    ///
60
    /// Transitions into:
61
    /// * [`State::Descriptors`]
62
    LoadConsensus,
63

            
64
    /// Downloads the most recent consensus from a directory authority.
65
    ///
66
    /// Transitions from:
67
    /// * Start, if no recent valid consensus exists in the database.
68
    /// * [`State::Descriptors`], if lifetime is over.
69
    /// * [`State::Hibernate`], if lifetime is over.
70
    ///
71
    /// Transitions into:
72
    /// * [`State::AuthCerts`], if we miss authority certificates.
73
    /// * [`State::StoreConsensus`], if all authority certificates exist in the
74
    ///   database.
75
    // TODO DIRMIRROR: What to do in the case of getting an invalid consensus
76
    // such as junk data?  The normal retry logic sounds reasonable here.
77
    FetchConsensus,
78

            
79
    /// Downloads, validates, and stores the missing authority certificates from
80
    /// the downloaded unvalidated consensus into the database.
81
    ///
82
    /// Transitions from:
83
    /// * [`State::FetchConsensus`], if we miss authority certificates.
84
    /// * [`State::AuthCerts`], if we still miss authority certificates.
85
    ///
86
    /// Transitions into:
87
    /// * [`State::AuthCerts`], if we still miss authority certificates.
88
    /// * [`State::StoreConsensus`], if we got all authority certificates.
89
    // TODO DIRMIRROR: What to do in the case of a MITM attack where an attacker
90
    // adds lots of invalid signature items at the bottom, leading to lots of
91
    // queries for directory authority certificates, which may succeed or not?
92
    // Best idea is probably to only download authcerts whose id fingerprints
93
    // are configured in our AuthorityContacts, because then we have an upper
94
    // limit.
95
    AuthCerts,
96

            
97
    /// Validates and stores the downloaded unvalidated consensus into the
98
    /// database.
99
    ///
100
    /// Transitions from:
101
    /// * [`State::FetchConsensus`], if we have all authority certificates.
102
    /// * [`State::AuthCerts`], if we have all authority certificates.
103
    ///
104
    /// Transitions into:
105
    /// * [`State::LoadConsensus`]
106
    StoreConsensus,
107

            
108
    /// Downloads missing network documents (descriptors) from a directory
109
    /// authority.
110
    ///
111
    /// Transitions from:
112
    /// * [`State::LoadConsensus`], if we initialize.
113
    /// * [`State::Descriptors`], if we still have missing descriptors left.
114
    ///
115
    /// Transitions into:
116
    /// * [`State::FetchConsensus`], if lifetime is over.
117
    /// * [`State::Descriptors`], if we still have missing descriptors left.
118
    /// * [`State::Hibernate`], if nothing is left.
119
    Descriptors,
120

            
121
    /// Hibernate because nothing is left.
122
    ///
123
    /// Transitions from:
124
    /// * [`State::Descriptors`]
125
    ///
126
    /// Transitions into:
127
    /// * [`State::FetchConsensus`], if the lifetime is over.
128
    Hibernate,
129
}
130

            
131
/// The execution engine for the finite state machine.
132
///
133
/// The states themselves are explained in [`State`].
134
///
135
/// This data structure itself is static and contains no state, but merely
136
/// configuration primitives that stay constant throughout the runtime of the
137
/// program, such as the [`AuthorityContacts`], and the
138
/// [`DirTolerance`].  It can be kept throughout the entire runtime and only
139
/// consists for convenience in order to not give each state machine related
140
/// (then static) method a super long signature containing these fields.
141
///
142
/// Besides these dynamic fields, there is also a generic parameter specifying
143
/// the consensus flavor that is being used.
144
///
145
/// The state itself is computed fully deterministically from the data found
146
/// within the database and [`ConsensusBoundData`].
147
///
148
/// This is the reason on why this structure is not called `StateMachine`,
149
/// because this implies that the type in itself carries state, which is not
150
/// true, because the state is stored entirely external, with this engine
151
/// only processing and modifying it.
152
///
153
/// See [`StaticEngine::determine_state()`] for more details.
154
#[derive(Debug)]
155
struct StaticEngine<T> {
156
    /// The authorities we are acknowledging.
157
    authorities: AuthorityContacts,
158

            
159
    /// The document tolerance we are accepting.
160
    tolerance: DirTolerance,
161

            
162
    /// The preferred runtime for compatibility with other arti crates.
163
    ///
164
    /// Generally obtained through [`PreferredRuntime::current()`].
165
    rt: PreferredRuntime,
166

            
167
    /// Utilizes the generic type parameter.
168
    _phantom: PhantomData<T>,
169
}
170

            
171
/// Additional state machine data concerning a single consensus.
172
///
173
/// This enum stores and keeps track of the consensus we are serving and in
174
/// which ✨state✨ it is currently in, such as whether it is verified or not,
175
/// or if we even have a state loaded in memory in the first place.
176
#[derive(Debug, Clone)]
177
enum ConsensusBoundData<T: FlavoredConsensusUnverified> {
178
    /// No state is loaded in memory at the moment.
179
    None,
180

            
181
    /// We have downloaded a consensus but it is not yet verified.
182
    Unverified {
183
        /// The unverified parsed consensus we have.
184
        // TODO DIRMIRROR: Make this optional, see comment in
185
        // StaticEngine::execute.
186
        consensus: T,
187

            
188
        /// The unparsed raw consensus we have.
189
        raw: String,
190
    },
191

            
192
    /// We have downloaded and verified a consensus.
193
    Verified {
194
        /// The verified consensus we have.
195
        consensus: T::Body,
196

            
197
        /// When to stop dealing with this consensus and fetching a new one.
198
        lifetime: Timestamp,
199

            
200
        /// SHA-1 digests of the missing server descriptors in the consensus.
201
        server_queue: HashSet<db::Sha1>,
202

            
203
        /// SHA-1 digests of the missing extra-info descriptors in the server
204
        /// descriptors of the consensus.
205
        ///
206
        /// extra-info documents are only transitively related to a consensus
207
        /// through consensus -> server descriptors -> extra-info descriptors
208
        extra_queue: HashSet<db::Sha1>,
209

            
210
        /// SHA-256 digests of the missing micro descriptors in the consensus.
211
        ///
212
        /// This field is technically mutually exclusive to server_queue and
213
        /// extra_queue because micro descriptors are only found in
214
        /// microdescriptor consensuses  and server plus extra-info
215
        /// descriptors only in plain consensuses.  However, because
216
        /// we used a queue based design, we just leave the queue empty instead
217
        /// of wrapping this behind an enum variant for true mutual exclusivity.
218
        /// This makes coding much easier with less boilerplate and neglectable
219
        /// additional runtime cost.
220
        micro_queue: HashSet<db::Sha256>,
221
    },
222
}
223

            
224
impl<T: FlavoredConsensusUnverified> StaticEngine<T> {
225
    /// Determines the [`State`] only from the database and [`ConsensusBoundData`].
226
    ///
227
    /// This method is fully idempotent, meaning it only depends upon the data
228
    /// found within the database and the [`ConsensusBoundData`]; there is no
229
    /// internal `state` variable or something contained within [`StaticEngine`].
230
6
    fn determine_state(
231
6
        &self,
232
6
        tx: &Transaction<'_>,
233
6
        data: &ConsensusBoundData<T>,
234
6
        now: Timestamp,
235
6
    ) -> Result<State, DatabaseError> {
236
        // Determine the state primarily upon ConsensusBoundData combined with
237
        // a few database queries, as well as the current time of course.
238
6
        let state = match data {
239
            // ConsensusBoundData::None means that we currently have no
240
            // consensus in memory.  This may be the case because we just
241
            // started up or because we just downloaded, validated, and inserted
242
            // a consensus into the database and reset ConsensusBoundData to
243
            // None afterwards.
244
            ConsensusBoundData::None => {
245
                // Check whether there is a valid consensus in the database at all.
246
                //
247
                // Yes, it is kinda redundant querying a consensus here
248
                // and potentially again when loading the consensus, but SQLite
249
                // is very fast and having to maintain two different queries,
250
                // one for checking and one for selecting, is prone to get
251
                // out-of-sync.
252
2
                match ConsensusMeta::<T>::query(tx, &self.tolerance, Some(now))?.as_slice() {
253
                    // Some consensus means we can load it.
254
2
                    [_, ..] => State::LoadConsensus,
255

            
256
                    // None means we must download it.
257
2
                    [] => State::FetchConsensus,
258
                }
259
            }
260

            
261
            // ConsensusBoundData::Unverified means that we recently downloaded
262
            // a consensus through State::FetchConsensus.  It is not fully
263
            // validated yet and we may not even be able due to missing
264
            // authority certificates.
265
4
            ConsensusBoundData::Unverified { consensus, .. } => {
266
                // Check whether there any missing authority certificates that
267
                // have signed the consensus.
268
4
                let missing_certs = !AuthCertMeta::query(
269
4
                    tx,
270
4
                    &consensus.sigs().signatories(),
271
4
                    &self.tolerance,
272
4
                    now,
273
                )?
274
                .1
275
4
                .is_empty();
276

            
277
4
                if missing_certs {
278
                    // Missing authority certificates means we must download
279
                    // them.
280
2
                    State::AuthCerts
281
                } else {
282
                    // If we have all authority certificates, we can validate
283
                    // and store it inside the database.
284
2
                    State::StoreConsensus
285
                }
286
            }
287

            
288
            // ConsensusBoundData::Verified means that we have successfully
289
            // loaded a recent valid consensus from the database using
290
            // State::LoadConsensus.  Depending on this, we download the missing
291
            // network documents (descriptors) from a directory authority, if
292
            // any.
293
            ConsensusBoundData::Verified {
294
                lifetime,
295
                server_queue: servers,
296
                extra_queue: extras,
297
                micro_queue: micros,
298
                ..
299
            } => {
300
                if *lifetime <= now {
301
                    // The lifetime has been surpassed, download a new
302
                    // consensus.  It is very important TO NOT transition to
303
                    // State::LoadConsensus here, because the current consensus
304
                    // may still be valid but not fresh anymore, in which case
305
                    // State::LoadConsensus will continue to obtain it from the
306
                    // database until valid-after has been surpassed, which is
307
                    // most definitely not what we want.
308
                    State::FetchConsensus
309
                } else if servers.is_empty() && extras.is_empty() && micros.is_empty() {
310
                    // All queues are empty, meaning we are done, until lifetime
311
                    // ends.
312
                    State::Hibernate
313
                } else {
314
                    // The lifetime has not been surpassed and we have stuff
315
                    // to download, so we need to obtain the descriptors.
316
                    State::Descriptors
317
                }
318
            }
319
        };
320
6
        Ok(state)
321
6
    }
322

            
323
    /// Executes a single state iteration in the finite state machine.
324
    ///
325
    /// The return value is of type [`Result<(), OperationError>`].
326
    /// The success type is not of much interest for calling applications.
327
    /// However, the error case itself should be passed towards
328
    /// [`crate::err::IsFatal::is_fatal()`] in order to either abort the
329
    /// application or retry with an appropriate timeout.
330
    ///
331
    // TODO: Use tracing instrumentation here.
332
    // TODO DIRMIRROR: Document the state transition check which we have to do
333
    // because of database invariances no longer holding true.
334
    async fn execute<R: Rng>(
335
        &self,
336
        pool: &Pool<SqliteConnectionManager>,
337
        data: &mut ConsensusBoundData<T>,
338
        endpoint: &[SocketAddr],
339
        now: Timestamp,
340
        rng: &mut R,
341
    ) -> Result<(), OperationError> {
342
        // TODO: Should we return DatabaseError or something like
343
        // StateDeterminationError?  Either way, both cases should be seriously
344
        // fatal.
345
        let state = db::read_tx(pool, |tx| self.determine_state(tx, data, now))??;
346
        debug!("state is {state}");
347

            
348
        match state {
349
            State::LoadConsensus => self.load_consensus(pool, data, now, rng),
350
            State::FetchConsensus => Ok(self.fetch_consensus(data, endpoint).await?),
351
            State::AuthCerts => self.auth_certs(pool, data, endpoint, now).await,
352
            State::StoreConsensus => todo!(),
353
            State::Descriptors => todo!(),
354
            State::Hibernate => self.hibernate(data, now).await,
355
        }
356
    }
357

            
358
    /// Executes [`State::LoadConsensus`].
359
    ///
360
    /// This method does the following:
361
    /// * Load the most recent valid consensus from the database.
362
    /// * Compute the lifetime for it.
363
    /// * Compute the missing descriptors for it.
364
2
    fn load_consensus<R: Rng>(
365
2
        &self,
366
2
        pool: &Pool<SqliteConnectionManager>,
367
2
        data: &mut ConsensusBoundData<T>,
368
2
        now: Timestamp,
369
2
        rng: &mut R,
370
2
    ) -> Result<(), OperationError> {
371
        // Load the most recent valid consensus from the database.
372
        //
373
        // If there is no consensus, we should have not entered the state, which
374
        // means that the database must have been externally verified.
375
        // In this case, it is probably better to return a bug, as external
376
        // applications arbitrarily modifying the database while we are running
377
        // leaves too much room for wrong/weird behavior.
378
2
        let (server_queue, extra_queue, micro_queue, lifetime, consensus) =
379
2
            db::read_tx(pool, |tx| {
380
2
                let meta = ConsensusMeta::<T>::query(tx, &self.tolerance, Some(now))?;
381
2
                let meta = meta
382
2
                    .first()
383
2
                    .ok_or(internal!("database externally modified?"))?;
384
2
                let server_queue = meta.missing_servers(tx)?;
385
2
                let extra_queue = meta.missing_extras(tx)?;
386
2
                let micro_queue = meta.missing_micros(tx)?;
387
2
                let lifetime = meta.lifetime(rng);
388
2
                let consensus = meta.data(tx)?;
389
2
                Ok::<_, DatabaseError>((
390
2
                    server_queue,
391
2
                    extra_queue,
392
2
                    micro_queue,
393
2
                    lifetime,
394
2
                    consensus,
395
2
                ))
396
2
            })??;
397

            
398
        // Parse the most recent valid consensus from the database.
399
        //
400
        // TODO DIRMIRROR:
401
        // Because only valid documents may exist in the database, it should
402
        // succeed.  However, there is this weird edge-case where we may have
403
        // inserted a document with a field we do not understand because of
404
        // using an old version.  After upgrading our version we may now
405
        // understand the field and realize it is wrong, leading to a violation
406
        // of this constraint.  Handling this is not very easy; I suppose adding
407
        // an additional column to the meta table storing the last used crate
408
        // version is a sensible idea, with upgrades and downgrades leading to
409
        // a parsing of all network documents within the database, throwing the
410
        // ones out we do not understand (anymore).
411
        //
412
        // See also the relevant MR discussion:
413
        // <https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/3664#note_3352723>
414
2
        let consensus = parse2::parse_netdoc::<T>(&ParseInput::new(&consensus, ""))
415
2
            .map_err(into_internal!("invalid netdoc in database?"))?
416
            // TODO DIRMIRROR: explain why this is OK, or re-verify the signatures
417
2
            .unwrap_unverified()
418
            .0;
419

            
420
2
        *data = ConsensusBoundData::Verified {
421
2
            consensus,
422
2
            lifetime,
423
2
            server_queue,
424
2
            extra_queue,
425
2
            micro_queue,
426
2
        };
427
2
        Ok(())
428
2
    }
429

            
430
    /// Fetches a consensus from an upstream authority.
431
    // TODO DIRMIRROR: Add logging.
432
    #[allow(clippy::string_slice)] // TODO
433
2
    async fn fetch_consensus(
434
2
        &self,
435
2
        data: &mut ConsensusBoundData<T>,
436
2
        endpoint: &[SocketAddr],
437
2
    ) -> Result<(), AuthorityRequestError> {
438
        // Obtain the consensus.
439
2
        let (raw, consensus) = self
440
2
            .send_request(endpoint, ConsensusRequest::new(T::flavor()))
441
2
            .await?;
442
2
        let mut consensus = consensus
443
2
            .into_iter()
444
2
            .map(|(doc, start, end)| (raw[start..end].to_owned(), doc))
445
2
            .collect::<VecDeque<_>>();
446

            
447
        // Check for the correct number of results.
448
2
        if consensus.len() != 1 {
449
            return Err(AuthorityRequestError::Response(
450
                "invalid number of consensus?",
451
            ));
452
2
        }
453

            
454
        // expect is fine because we checked the length for one above.
455
2
        let (raw, consensus) = consensus.pop_front().expect("pop_front");
456

            
457
        // And store it.
458
2
        *data = ConsensusBoundData::Unverified { consensus, raw };
459

            
460
2
        Ok(())
461
2
    }
462

            
463
    /// Fetches, validates, and stores authority certificates.
464
    //
465
    // TODO DIRMIRROR: Right now, there is a torspec DoS issue.
466
    // An attacker may add lots of garbage signatures and we will fetch them
467
    // Even checking the ID PK against v3idents is not useful because an
468
    // attacker may still use the same ID PK dozens of times with various
469
    // SK PKs.  A good fix would include checking that no ID PK is duplicate
470
    // AND to ignore all ID PKs we do not recognize.  Also, it would probably
471
    // be best to move the v3idents structure to a HashMap based implementation,
472
    // as well as the signatories result.
473
    #[allow(clippy::string_slice)] // TODO
474
2
    async fn auth_certs(
475
2
        &self,
476
2
        pool: &Pool<SqliteConnectionManager>,
477
2
        data: &mut ConsensusBoundData<T>,
478
2
        endpoint: &[SocketAddr],
479
2
        now: Timestamp,
480
2
    ) -> Result<(), OperationError> {
481
        // Obtain the signatories of the current unverified consensus.
482
2
        let signatories = match data {
483
2
            ConsensusBoundData::Unverified { consensus, .. } => consensus.sigs().signatories(),
484
            _ => return Err(OperationError::Bug(internal!("data is not unverified"))),
485
        };
486

            
487
        // Obtain the missing certificate identifiers.
488
2
        let (_, missing) = db::read_tx(pool, |tx| {
489
2
            AuthCertMeta::query(tx, &signatories, &self.tolerance, now)
490
2
        })??;
491
2
        if missing.is_empty() {
492
            // Although not technically fatal, retrying when the database was
493
            // externally modified does not make much sense.
494
            return Err(OperationError::Bug(internal!(
495
                "database externally modified?"
496
            )));
497
2
        }
498

            
499
        // Compose the request.
500
2
        let mut requ = AuthCertRequest::new();
501
8
        for kp in missing.iter().copied() {
502
8
            requ.push(kp);
503
8
        }
504

            
505
        // Fire it off.
506
2
        let (resp, certs) = self
507
2
            .send_request::<_, AuthCertUnverified>(endpoint, requ)
508
2
            .await?;
509

            
510
        // Verify each certificate.  Invalid certificates and other problems get
511
        // logged and filtered out, with the result being then inserted into
512
        // the database.
513
2
        let certs = certs
514
2
            .into_iter()
515
8
            .filter_map(|(unverified, start, end)| {
516
8
                let unverified_body = unverified.inspect_unverified().0;
517
8
                let kp = AuthCertKeyIds {
518
8
                    id_fingerprint: unverified_body.dir_identity_key.to_rsa_identity(),
519
8
                    sk_fingerprint: unverified_body.dir_signing_key.to_rsa_identity(),
520
8
                };
521

            
522
                // Skip certificates we did not asked for.
523
                //
524
                // Not much of an issue because certificate verification will
525
                // usually fail anyways, except for this weird edge-case where we
526
                // actually have that id fingerprint in the v3idents.
527
8
                if !missing.contains(&kp) {
528
                    debug!("authority returned certificate we did not asked for: {kp:?}");
529
                    return None;
530
8
                }
531

            
532
8
                let verified = unverified
533
8
                    .verify(self.authorities.v3idents())
534
8
                    .and_then(|v| {
535
8
                        Ok(self
536
8
                            .tolerance
537
8
                            .extend_tolerance(v)
538
8
                            .if_valid_at(&now.into())?)
539
8
                    });
540
8
                let verified = match verified {
541
8
                    Ok(v) => v,
542
                    Err(e) => {
543
                        // TODO DIRMIRROR: Log the actual cert.
544
                        warn!("received invalid auth cert: {e}",);
545
                        return None;
546
                    }
547
                };
548

            
549
8
                Some((verified, &resp[start..end]))
550
8
            })
551
2
            .collect::<Vec<_>>();
552

            
553
        // When we have reached this, it means that this call made no progress,
554
        // i.e. the authority only returned certificates we were not interested
555
        // in.
556
2
        if certs.is_empty() {
557
            Err(Box::new(AuthorityRequestError::Response(
558
                "response lead to no progress",
559
            )))?;
560
2
        }
561

            
562
        // Finally, insert them all into the database.
563
2
        db::rw_tx(pool, |tx| {
564
8
            for (cert, data) in certs {
565
8
                AuthCertMeta::insert(tx, ContentEncoding::iter(), &cert, data)?;
566
            }
567
2
            Ok::<_, DatabaseError>(())
568
2
        })??;
569

            
570
2
        Ok(())
571
2
    }
572

            
573
    /// Hibernates for the remaining lifetime of the consensus.
574
    async fn hibernate(
575
        &self,
576
        data: &mut ConsensusBoundData<T>,
577
        now: Timestamp,
578
    ) -> Result<(), OperationError> {
579
        match data {
580
            ConsensusBoundData::None | ConsensusBoundData::Unverified { .. } => {
581
                // This should not happen, we only enter hibernation in a state
582
                // that already has a verified consensus.
583
                return Err(internal!("hibernating without a verified consensus?").into());
584
            }
585
            ConsensusBoundData::Verified { lifetime, .. } => {
586
                let timeout = *lifetime - now;
587
                debug!("hibernating for {}s", timeout.as_secs());
588
                tokio::time::sleep(timeout).await;
589
            }
590
        }
591

            
592
        Ok(())
593
    }
594

            
595
    /// Convenience wrapper around [`tor_dirclient::send_request()`].
596
    ///
597
    /// It opens a TCP connection, performs the request, and parses the result.
598
    ///
599
    /// Returns the raw response alongside the output of
600
    /// [`parse2::parse_netdoc_multiple_sophisticated()`] with the results being
601
    /// filtered.
602
    ///
603
    /// Invalid documents are ignored but a warning is logged.
604
    ///
605
    /// The output is required because we need the raw document alongside the
606
    /// offsets to have the actual data we will insert into the database later
607
    /// on.
608
4
    async fn send_request<R: Requestable, D: NetdocParseable>(
609
4
        &self,
610
4
        endpoint: &[SocketAddr],
611
4
        requ: R,
612
4
    ) -> Result<(String, Vec<(D, usize, usize)>), AuthorityRequestError> {
613
        // The check is required to not let Tokio panic.
614
4
        if endpoint.is_empty() {
615
            return Err(AuthorityRequestError::Bug(internal!("empty endpoint?")));
616
4
        }
617

            
618
        // Open the TCP connection.
619
4
        let mut stream = TcpStream::connect(endpoint)
620
4
            .await
621
4
            .map_err(AuthorityRequestError::TcpConnect)?
622
4
            .compat();
623

            
624
        // Perform the request and map the result nicely.
625
4
        let resp = tor_dirclient::send_request(&self.rt, &requ, &mut stream, None)
626
4
            .await
627
4
            .map(|resp| resp.output_string().map(|resp| resp.to_owned()));
628

            
629
        // We can immediately drop the connection now, no need to occupy even
630
        // more resources from the authority.  Doing so is fine, it is HTTP/1.0
631
        // and there is no connection reuse anyways.
632
4
        drop(stream);
633

            
634
        // Returning all request failed errors is okay; they all imply that
635
        // retrying from a different authority is fine.
636
        // TODO MSRV: If possible, use Result::flatten once MSRV 1.89.
637
4
        let resp = match resp {
638
4
            Ok(Ok(r)) => Ok(r),
639
            Ok(Err(e)) => Err(e),
640
            Err(tor_dirclient::Error::RequestFailed(e)) => Err(e),
641
            Err(e) => {
642
                return Err(AuthorityRequestError::Bug(internal!(
643
                    "unhandled dirclient error: {e}"
644
                )));
645
            }
646
        }?;
647

            
648
        // Parse the response.
649
4
        let parsed = parse2::parse_netdoc_multiple_sophisticated(&ParseInput::new(&resp, ""))?
650
4
            .into_iter()
651
10
            .filter_map(|(res, start, end)| match res {
652
10
                Ok(doc) => Some((doc, start, end)),
653
                Err(e) => {
654
                    debug!("ignoring invalid netdoc: {e}");
655
                    None
656
                }
657
10
            })
658
4
            .collect();
659

            
660
4
        Ok((resp, parsed))
661
4
    }
662
}
663

            
664
#[cfg(test)]
665
mod test {
666
    // @@ begin test lint list maintained by maint/add_warning @@
667
    #![allow(clippy::bool_assert_comparison)]
668
    #![allow(clippy::clone_on_copy)]
669
    #![allow(clippy::dbg_macro)]
670
    #![allow(clippy::mixed_attributes_style)]
671
    #![allow(clippy::print_stderr)]
672
    #![allow(clippy::print_stdout)]
673
    #![allow(clippy::single_char_pattern)]
674
    #![allow(clippy::unwrap_used)]
675
    #![allow(clippy::unchecked_time_subtraction)]
676
    #![allow(clippy::useless_vec)]
677
    #![allow(clippy::needless_pass_by_value)]
678
    #![allow(clippy::string_slice)] // See arti#2571
679
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
680

            
681
    use rusqlite::params;
682
    use tokio::{
683
        io::{AsyncReadExt, AsyncWriteExt},
684
        net::TcpListener,
685
    };
686
    use tor_basic_utils::test_rng::testing_rng;
687

            
688
    use crate::{database::sql, testdata2};
689

            
690
    use super::*;
691

            
692
    type Plain = tor_netdoc::doc::netstatus::plain::NetworkStatusUnverified;
693
    type Md = tor_netdoc::doc::netstatus::md::NetworkStatusUnverified;
694

            
695
    /// Tests whether the load consensus state computes missing descriptors
696
    /// properly.
697
    ///
698
    /// For this, the test removes a present router descriptor from the storage
699
    /// to verify that it is detected as missing and added to the download
700
    /// queue.
701
    #[tokio::test]
702
    async fn state_load_consensus() {
703
        let pool = testdata2::test_db();
704
        let mut data = ConsensusBoundData::<Plain>::None;
705
        let engine = StaticEngine {
706
            authorities: testdata2::current_auth_cert_contacts(),
707
            tolerance: DirTolerance::default(),
708
            rt: PreferredRuntime::current().unwrap(),
709
            _phantom: Default::default(),
710
        };
711

            
712
        let time: Timestamp = testdata2::valid_system_time().into();
713
        let fresh_until: Timestamp = testdata2::current_consensus_ns()
714
            .0
715
            .preamble
716
            .lifetime
717
            .fresh_until
718
            .0
719
            .into();
720
        let valid_until: Timestamp = testdata2::current_consensus_ns()
721
            .0
722
            .preamble
723
            .lifetime
724
            .valid_until
725
            .0
726
            .into();
727
        // This is the middle of valid_until and fresh_until.
728
        let fresh_until_half = fresh_until + ((valid_until - fresh_until) / 2);
729

            
730
        // Remove a single router descriptor from our storage to see whether it
731
        // appears in the download queue as expected.
732
        let relay_to_remove = &testdata2::current_consensus_ns().0.routers[0];
733
        let relay_to_remove = db::Sha1::from(*relay_to_remove.doc_digest());
734
        pool.get()
735
            .unwrap()
736
            .execute(
737
                sql!("DELETE FROM router_descriptor WHERE unsigned_sha1 = ?1"),
738
                params![relay_to_remove],
739
            )
740
            .unwrap();
741

            
742
        engine
743
            .load_consensus(&pool, &mut data, time, &mut testing_rng())
744
            .unwrap();
745

            
746
        // El-cheapo assert_eq due to lack of PartialEq for tor-netdoc poc.
747
        match data {
748
            ConsensusBoundData::Verified {
749
                lifetime,
750
                server_queue,
751
                extra_queue,
752
                micro_queue,
753
                ..
754
            } => {
755
                // If everything worked properly, then the queue should only
756
                // contain the relay we removed, because that is missing now.
757
                assert_eq!(server_queue, HashSet::from([relay_to_remove]));
758
                assert!(lifetime >= fresh_until);
759
                assert!(lifetime <= fresh_until_half);
760
                assert!(extra_queue.is_empty());
761
                assert!(micro_queue.is_empty());
762
            }
763
            _ => panic!("data is not verified"),
764
        }
765
    }
766

            
767
    /// Tests whether the fetch consensus state properly fetches a consensus
768
    /// and keeps it in memory as unverified.
769
    ///
770
    /// For this, we spawn a tokio task simulating a web server which responds
771
    /// with a consensus.
772
    #[tokio::test]
773
    async fn state_fetch_consensus() {
774
        let pool = testdata2::test_db();
775
        let mut data = ConsensusBoundData::<Plain>::None;
776
        let engine = StaticEngine {
777
            authorities: testdata2::current_auth_cert_contacts(),
778
            tolerance: DirTolerance::default(),
779
            rt: PreferredRuntime::current().unwrap(),
780
            _phantom: Default::default(),
781
        };
782

            
783
        let state = db::read_tx(&pool, |tx| {
784
            engine.determine_state(tx, &data, testdata2::invalid_system_time().into())
785
        })
786
        .unwrap()
787
        .unwrap();
788
        assert_eq!(state, State::FetchConsensus);
789

            
790
        let server = TcpListener::bind("[::1]:0").await.unwrap();
791
        let saddr = server.local_addr().unwrap();
792
        tokio::spawn(async move {
793
            let (mut stream, _) = server.accept().await.unwrap();
794
            let mut buf = vec![0; 1024];
795
            let _ = stream.read(&mut buf).await.unwrap();
796

            
797
            let consensus = testdata2::current_consensus_ns().1;
798
            let resp = format!(
799
                "HTTP/1.0 200 OK\r\nContent-Encoding: identity\r\nContent-Length: {}\r\n\r\n{consensus}",
800
                consensus.len()
801
            );
802
            stream.write_all(resp.as_bytes()).await.unwrap();
803
        });
804

            
805
        engine.fetch_consensus(&mut data, &[saddr]).await.unwrap();
806
        match data {
807
            ConsensusBoundData::Unverified { raw, .. } => {
808
                assert_eq!(raw, testdata2::current_consensus_ns().1);
809
            }
810
            _ => panic!("data is not unverified"),
811
        }
812
    }
813

            
814
    /// Tests the download, verification, and insertion of authority certificates.
815
    ///
816
    /// For this, it starts by removing an existing one from the test database
817
    /// to see it getting re-downloaded, re-verified, and re-inserted again.
818
    #[tokio::test]
819
    async fn state_auth_certs() {
820
        let pool = testdata2::test_db();
821
        let mut data = ConsensusBoundData::<Plain>::Unverified {
822
            consensus: parse2::parse_netdoc(&ParseInput::new(
823
                testdata2::current_consensus_ns().1,
824
                "",
825
            ))
826
            .unwrap(),
827
            raw: testdata2::current_consensus_ns().1.to_owned(),
828
        };
829
        let engine = StaticEngine {
830
            authorities: testdata2::current_auth_cert_contacts(),
831
            tolerance: DirTolerance::default(),
832
            rt: PreferredRuntime::current().unwrap(),
833
            _phantom: Default::default(),
834
        };
835

            
836
        // We want to download authority certificates; for this, remove
837
        // one of them from the database.
838
        pool.get()
839
            .unwrap()
840
            .execute(
841
                sql!(
842
                    "
843
                    DELETE FROM authority_key_certificate
844
                    WHERE :kp_auth_id_rsa_sha1 = ?1
845
                    "
846
                ),
847
                params![db::Sha1::from(
848
                    testdata2::current_auth_cert_ids()[0].to_bytes()
849
                )],
850
            )
851
            .unwrap();
852

            
853
        assert_eq!(
854
            db::read_tx(&pool, |tx| engine.determine_state(
855
                tx,
856
                &data,
857
                testdata2::valid_system_time().into()
858
            ))
859
            .unwrap()
860
            .unwrap(),
861
            State::AuthCerts
862
        );
863

            
864
        let server = TcpListener::bind("[::1]:0").await.unwrap();
865
        let saddr = server.local_addr().unwrap();
866
        tokio::spawn(async move {
867
            let mut buf = [0; 1024];
868
            let (mut stream, _) = server.accept().await.unwrap();
869
            let _ = stream.read(&mut buf).await.unwrap();
870

            
871
            let authcerts = testdata2::current_auth_certs()
872
                .into_iter()
873
                .map(|x| x.1)
874
                .collect::<String>();
875

            
876
            stream.write_all(format!(
877
                "HTTP/1.0 200 OK\r\nContent-Encoding: identity\r\nContent-Length: {}\r\n\r\n{authcerts}",
878
                authcerts.len()
879
            ).as_bytes()).await.unwrap();
880
        });
881

            
882
        // Fetch all authcerts.
883
        engine
884
            .auth_certs(
885
                &pool,
886
                &mut data,
887
                &[saddr],
888
                testdata2::valid_system_time().into(),
889
            )
890
            .await
891
            .unwrap();
892

            
893
        // Check whether we are done with all authcerts.
894
        assert_eq!(
895
            db::read_tx(&pool, |tx| engine.determine_state(
896
                tx,
897
                &data,
898
                testdata2::valid_system_time().into(),
899
            ))
900
            .unwrap()
901
            .unwrap(),
902
            State::StoreConsensus
903
        );
904
        let recent_authcerts = db::read_tx(&pool, |tx| {
905
            AuthCertMeta::query(
906
                tx,
907
                &parse2::parse_netdoc::<Plain>(&ParseInput::new(
908
                    testdata2::current_consensus_ns().1,
909
                    "",
910
                ))
911
                .unwrap()
912
                .sigs()
913
                .signatories(),
914
                &DirTolerance::default(),
915
                testdata2::valid_system_time().into(),
916
            )
917
        })
918
        .unwrap()
919
        .unwrap();
920
        // TODO DIRMIRROR: Compare more than just length.
921
        assert_eq!(
922
            recent_authcerts.0.len(),
923
            engine.authorities.v3idents().len()
924
        );
925
        assert!(recent_authcerts.1.is_empty());
926
    }
927
}