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
    fmt::Debug,
21
    hash::Hash,
22
    marker::PhantomData,
23
    mem,
24
    net::SocketAddr,
25
    time::Duration,
26
};
27

            
28
use educe::Educe;
29
use r2d2::Pool;
30
use r2d2_sqlite::SqliteConnectionManager;
31
use rand::Rng;
32
use rusqlite::Transaction;
33
use tokio::net::TcpStream;
34
use tokio_util::compat::TokioAsyncReadCompatExt;
35
use tor_checkable::TimeBound;
36
use tor_dirclient::request::{AuthCertRequest, ConsensusRequest, Requestable};
37
use tor_dircommon::{authority::AuthorityContacts, config::DirTolerance};
38
use tor_error::internal;
39
use tor_netdoc::{
40
    doc::{
41
        authcert::{AuthCert, AuthCertKeyIds, AuthCertUnverified},
42
        netstatus::ConsensusVerifiabilityError,
43
    },
44
    parse2::{self, NetdocParseable, NetdocParseableUnverified, ParseInput, VerifyFailed},
45
};
46
use tor_rtcompat::PreferredRuntime;
47
use tracing::debug;
48

            
49
use crate::{
50
    database::{self as db, AuthCertMeta, ConsensusMeta, ContentEncoding, Sha1, Sha256, Timestamp},
51
    err::{AuthorityRequestError, DatabaseError, OperationError},
52
    types::FlavoredConsensusUnverified,
53
};
54

            
55
mod poc;
56

            
57
/// The duration to try verifying an unverified consensus.
58
///
59
/// This exists primarily to properly handle the
60
/// [`ConsensusVerifiabilityError::InsufficientTrustedSigners`] error.  Besides,
61
/// it also serves as an upper-limit for the time we can spend in the phase for
62
/// fetching authority certificates, which could otherwise run for a very long
63
/// time, as long as every request makes progress and finishes before a
64
/// connection timeout is reached.
65
///
66
/// Obviously, we cannot use the values inside the consensus, as they are
67
/// untrusted.
68
const UNVERIFIED_TTL: Duration = Duration::from_mins(10);
69

            
70
/// The endpoint(s) of a download authority identifying it.
71
type DownloadAuthority = [SocketAddr];
72

            
73
/// The various states for the [`StaticEngine`].
74
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
75
enum State {
76
    /// Loads the most recent valid (and verified) consensus from the database
77
    /// into memory.
78
    ///
79
    /// Transitions from:
80
    /// * Start, if a recent valid consensus exists in the database.
81
    /// * [`State::StoreConsensus`], if successfully finished.
82
    ///
83
    /// Transitions into:
84
    /// * [`State::Descriptors`]
85
    LoadConsensus,
86

            
87
    /// Downloads the most recent consensus from a directory authority.
88
    ///
89
    /// Transitions from:
90
    /// * Start, if no recent valid consensus exists in the database.
91
    /// * [`State::Descriptors`], if ttl is over.
92
    /// * [`State::Hibernate`], if ttl is over.
93
    ///
94
    /// Transitions into:
95
    /// * [`State::AuthCerts`], if we miss authority certificates.
96
    /// * [`State::StoreConsensus`], if all authority certificates exist in the
97
    ///   database.
98
    /// * [`State::Hibernate`], if we have insufficient trusted authorities.
99
    // TODO DIRMIRROR: What to do in the case of getting an invalid consensus
100
    // such as junk data?  The normal retry logic sounds reasonable here.
101
    FetchConsensus,
102

            
103
    /// Downloads, validates, and stores the missing authority certificates from
104
    /// the downloaded unvalidated consensus into the database.
105
    ///
106
    /// Transitions from:
107
    /// * [`State::FetchConsensus`], if we miss authority certificates.
108
    /// * [`State::AuthCerts`], if we still miss authority certificates.
109
    ///
110
    /// Transitions into:
111
    /// * [`State::AuthCerts`], if we still miss authority certificates.
112
    /// * [`State::StoreConsensus`], if we got all authority certificates.
113
    AuthCerts,
114

            
115
    /// Validates and stores the downloaded unvalidated consensus into the
116
    /// database.
117
    ///
118
    /// Transitions from:
119
    /// * [`State::FetchConsensus`], if we have all authority certificates.
120
    /// * [`State::AuthCerts`], if we have all authority certificates.
121
    ///
122
    /// Transitions into:
123
    /// * [`State::LoadConsensus`]
124
    StoreConsensus,
125

            
126
    /// Downloads missing network documents (descriptors) from a directory
127
    /// authority.
128
    ///
129
    /// Transitions from:
130
    /// * [`State::LoadConsensus`], if we initialize.
131
    /// * [`State::Descriptors`], if we still have missing descriptors left.
132
    ///
133
    /// Transitions into:
134
    /// * [`State::FetchConsensus`], if ttl is over.
135
    /// * [`State::Descriptors`], if we still have missing descriptors left.
136
    /// * [`State::Hibernate`], if nothing is left.
137
    Descriptors,
138

            
139
    /// Hibernate because nothing is left.
140
    ///
141
    /// Transitions from:
142
    /// * [`State::FetchConsensus`], if we have insufficient trusted
143
    ///   authorities.
144
    /// * [`State::Descriptors`], if we have downloaded all descriptors.
145
    ///
146
    /// Transitions into:
147
    /// * [`State::FetchConsensus`], if the ttl is over.
148
    Hibernate,
149
}
150

            
151
/// The execution engine for the finite state machine.
152
///
153
/// The states themselves are explained in [`State`].
154
///
155
/// This data structure itself is static and contains no state, but merely
156
/// configuration primitives that stay constant throughout the runtime of the
157
/// program, such as the [`AuthorityContacts`], and the
158
/// [`DirTolerance`].  It can be kept throughout the entire runtime and only
159
/// consists for convenience in order to not give each state machine related
160
/// (then static) method a super long signature containing these fields.
161
///
162
/// Besides these dynamic fields, there is also a generic parameter specifying
163
/// the consensus flavor that is being used.
164
///
165
/// The state itself is computed fully deterministically from the data found
166
/// within the database and [`ConsensusBoundData`].
167
///
168
/// This is the reason on why this structure is not called `StateMachine`,
169
/// because this implies that the type in itself carries state, which is not
170
/// true, because the state is stored entirely external, with this engine
171
/// only processing and modifying it.
172
///
173
/// See [`StaticEngine::determine_state()`] for more details.
174
#[derive(Debug)]
175
struct StaticEngine<T> {
176
    /// The authorities we are acknowledging.
177
    authorities: AuthorityContacts,
178

            
179
    /// The document tolerance we are accepting.
180
    tolerance: DirTolerance,
181

            
182
    /// The preferred runtime for compatibility with other arti crates.
183
    ///
184
    /// Generally obtained through [`PreferredRuntime::current()`].
185
    rt: PreferredRuntime,
186

            
187
    /// The content encodings we apply to documents.
188
    encodings: HashSet<ContentEncoding>,
189

            
190
    /// Utilizes the generic type parameter.
191
    _phantom: PhantomData<T>,
192
}
193

            
194
/// Additional state machine data concerning a single consensus.
195
///
196
/// This data type exists to store data that cannot be stored in the database,
197
/// including the consensus that is currently verified, the consensus that is
198
/// currently served, the timestamp until we will serve it, etc.
199
///
200
/// # Field Requirements
201
///
202
/// In order for a field to be contained here, at least one of the following
203
/// criteria should be met:
204
/// * The item needs to persist between multiple invocation and cannot be
205
///   obtained from the database.
206
/// * The item itself serves as an identifier to the database and querying it
207
///   it again can not be done in a pure fashion, e.g. a [`ConsensusMeta`]
208
///   identifier.
209
/// * The item exists purely for optimization purposes and can be thrown away
210
///   at any time without data loss, such as content-addressable caches.
211
///   These caches must be pure in the sense that a cache miss must always
212
///   return the same result as a cache hit and vice versa.
213
//
214
// TODO DIRMIRROR: This is probably better of as a struct instead of an enum.
215
#[derive(Debug, Clone)]
216
enum ConsensusBoundData<T: FlavoredConsensusUnverified> {
217
    /// No state is loaded in memory at the moment.
218
    None,
219

            
220
    /// We have downloaded a consensus but it is not yet verified.
221
    Unverified {
222
        /// The unverified parsed consensus we have.
223
        consensus: T,
224

            
225
        /// The unparsed raw consensus we have.
226
        raw: String,
227

            
228
        /// When to stop dealing with this consensus and download again.
229
        ///
230
        /// Useful to have in order to not stuck forever in a verification
231
        /// loop, i.e. due to unavailable authentication certificates, etc.
232
        ttl: Timestamp,
233
    },
234

            
235
    /// We have downloaded and verified a consensus.
236
    Verified {
237
        /// The verified consensus we have.
238
        ///
239
        /// Contained in this structure because it is the only way we can
240
        /// uniquely identify a consensus, which is crucial for operation, as
241
        /// things like the `ttl` are derived from it.
242
        ///
243
        /// While methods like [`ConsensusMeta::query`] would probably also
244
        /// work correctly, relying on them feels rather wrong, as they were
245
        /// not made to be pure.
246
        consensus: ConsensusMeta<T>,
247

            
248
        /// When to stop dealing with this consensus and fetching a new one.
249
        ttl: Timestamp,
250

            
251
        /// Descriptors we refuse to further download.
252
        ///
253
        /// Right now, this state gets cleaned, everytime we restart or obtain
254
        /// a new consensus.  This is probably okay, as we most likely do not
255
        /// want to store it on disk.
256
        futile: Box<FutileDescriptors>,
257
    },
258
}
259

            
260
impl<T: FlavoredConsensusUnverified> StaticEngine<T> {
261
    /// Determines the [`State`] only from the database and [`ConsensusBoundData`].
262
    ///
263
    /// This method is fully idempotent, meaning it only depends upon the data
264
    /// found within the database and the [`ConsensusBoundData`]; there is no
265
    /// internal `state` variable or something contained within [`StaticEngine`].
266
6
    fn determine_state(
267
6
        &self,
268
6
        tx: &Transaction<'_>,
269
6
        data: &ConsensusBoundData<T>,
270
6
        now: Timestamp,
271
6
    ) -> Result<State, DatabaseError> {
272
        // Determine the state primarily upon ConsensusBoundData combined with
273
        // a few database queries, as well as the current time of course.
274
6
        let state = match data {
275
            // ConsensusBoundData::None means that we currently have no
276
            // consensus in memory.  This may be the case because we just
277
            // started up or because we just downloaded, validated, and inserted
278
            // a consensus into the database and reset ConsensusBoundData to
279
            // None afterwards.
280
            ConsensusBoundData::None => {
281
                // Check whether there is a valid consensus in the database at all.
282
2
                match self.recent_consensus(tx, now)? {
283
                    // Some consensus means we can load it.
284
1
                    Some(_) => State::LoadConsensus,
285

            
286
                    // None means we must download it.
287
1
                    None => State::FetchConsensus,
288
                }
289
            }
290

            
291
            // ConsensusBoundData::Unverified means that we recently downloaded
292
            // a consensus through State::FetchConsensus.  It is not fully
293
            // validated yet and we may not even be able due to missing
294
            // authority certificates.
295
4
            ConsensusBoundData::Unverified { consensus, ttl, .. } if now < *ttl => {
296
4
                let certs_already = self.certs_already(tx, now)?;
297
4
                match consensus.can_verify(self.authorities.v3idents(), &certs_already) {
298
                    // We can verify and insert the consensus.
299
2
                    Ok(()) => State::StoreConsensus,
300

            
301
                    // We will never be able to verify the consensus, better
302
                    // times may come ...
303
                    Err(ConsensusVerifiabilityError::InsufficientTrustedSigners) => {
304
1
                        State::Hibernate
305
                    }
306

            
307
                    // We need to fetch more authority certificates from the
308
                    // network.
309
1
                    Err(ConsensusVerifiabilityError::MissingAuthCerts { .. }) => State::AuthCerts,
310
                }
311
            }
312

            
313
            ConsensusBoundData::Unverified { .. } => State::FetchConsensus,
314

            
315
            // ConsensusBoundData::Verified means that we have successfully
316
            // loaded a recent valid consensus from the database using
317
            // State::LoadConsensus.  Depending on this, we download the missing
318
            // network documents (descriptors) from a directory authority, if
319
            // any.
320
            ConsensusBoundData::Verified {
321
                consensus,
322
                ttl,
323
                futile,
324
            } => {
325
                // See if there are any missing descriptors that are not Refused
326
                // yet.  It is important to not exclude the NotFound descriptors
327
                // here because they may be available at a different authority.
328
                //
329
                // TODO DIRMIRROR: What to do in the case there is no authority
330
                // left in our list of endpoints?  Right now, this module of the
331
                // code is not aware of the endpoints to try and it is the
332
                // responsibility of the caller to check this.  Perhaps we
333
                // should provide a possibility to force hibernate, as in passing
334
                // an Option<DownloadAuthority> or an empty list.  An alternative
335
                // worth exploring might also be to perform the RetryDelay
336
                // wait logic, as in to not let the caller perform any timeouts
337
                // themselves.  Either way, we want to wait to hibernate and
338
                // wait for the next consensus in the case that no authority
339
                // is left.
340
                //
341
                // TODO DIRMIRROR: This appears to be prone to copy and paste
342
                // errors, we should do something about it, as in macros and/or
343
                // traits.
344
                let (missing_routers, missing_micros, missing_extras) = (
345
                    consensus.missing_routers(tx, futile.routers.refused(), Some(1))?,
346
                    consensus.missing_micros(tx, futile.micros.refused(), Some(1))?,
347
                    consensus.missing_extras(tx, futile.extras.refused(), Some(1))?,
348
                );
349

            
350
                if *ttl <= now {
351
                    // The ttl has been surpassed, download a new
352
                    // consensus.  It is very important TO NOT transition to
353
                    // State::LoadConsensus here, because the current consensus
354
                    // may still be valid but not fresh anymore, in which case
355
                    // State::LoadConsensus will continue to obtain it from the
356
                    // database until valid-after has been surpassed, which is
357
                    // most definitely not what we want.
358
                    State::FetchConsensus
359
                } else if missing_routers.is_empty()
360
                    && missing_micros.is_empty()
361
                    && missing_extras.is_empty()
362
                {
363
                    // All queues are empty, meaning we are done, until ttl
364
                    // ends.
365
                    State::Hibernate
366
                } else {
367
                    // The ttl has not been surpassed and we have stuff
368
                    // to download, so we need to obtain the descriptors.
369
                    State::Descriptors
370
                }
371
            }
372
        };
373
6
        Ok(state)
374
6
    }
375

            
376
    /// Executes a single state iteration in the finite state machine.
377
    ///
378
    /// The return value is of type [`Result<(), OperationError>`].
379
    /// The success type is not of much interest for calling applications.
380
    /// However, the error case itself should be passed towards
381
    /// [`crate::err::IsFatal::is_fatal()`] in order to either abort the
382
    /// application or retry with an appropriate timeout.
383
    ///
384
    // TODO: Use tracing instrumentation here.
385
    // TODO DIRMIRROR: Document the state transition check which we have to do
386
    // because of database invariances no longer holding true.
387
    async fn execute<R: Rng>(
388
        &self,
389
        pool: &Pool<SqliteConnectionManager>,
390
        data: &mut ConsensusBoundData<T>,
391
        endpoint: &DownloadAuthority,
392
        now: Timestamp,
393
        rng: &mut R,
394
    ) -> Result<(), OperationError> {
395
        // TODO DIRMIRROR: Right now, we pass around the pool object which is
396
        // not very nice.  Instead, we should match based on the following
397
        // criteria: Needs write transaction, needs read transaction, needs
398
        // pool, needs no database access.
399

            
400
        // TODO: Should we return DatabaseError or something like
401
        // StateDeterminationError?  Either way, both cases should be seriously
402
        // fatal.
403
        let state = db::read_tx(pool, |tx| self.determine_state(tx, data, now))??;
404
        debug!("state is {state}");
405

            
406
        match state {
407
            State::LoadConsensus => self.load_consensus(pool, data, now, rng),
408
            State::FetchConsensus => Ok(self.fetch_consensus(data, endpoint, now).await?),
409
            State::AuthCerts => self.auth_certs(pool, data, endpoint, now).await,
410
            State::StoreConsensus => self.store_consensus(pool, data, now),
411
            State::Descriptors => todo!(),
412
            State::Hibernate => self.hibernate(data, now).await,
413
        }
414
    }
415

            
416
    /// Executes [`State::LoadConsensus`].
417
    ///
418
    /// This method does the following:
419
    /// * Load the most recent valid consensus from the database.
420
    /// * Compute the ttl for it.
421
    /// * Compute the missing descriptors for it.
422
2
    fn load_consensus<R: Rng>(
423
2
        &self,
424
2
        pool: &Pool<SqliteConnectionManager>,
425
2
        data: &mut ConsensusBoundData<T>,
426
2
        now: Timestamp,
427
2
        rng: &mut R,
428
2
    ) -> Result<(), OperationError> {
429
        // Load the most recent valid consensus from the database.
430
        //
431
        // If there is no consensus, we should have not entered the state, which
432
        // means that the database must have been externally verified.
433
        // In this case, it is probably better to return a bug, as external
434
        // applications arbitrarily modifying the database while we are running
435
        // leaves too much room for wrong/weird behavior.
436
2
        let consensus = db::read_tx(pool, |tx| self.recent_consensus(tx, now))??
437
2
            .ok_or_else(|| internal!("database externally modified?"))?;
438
2
        let ttl = consensus.ttl(rng);
439
2
        let futile = Box::new(FutileDescriptors::default());
440

            
441
2
        *data = ConsensusBoundData::Verified {
442
2
            consensus,
443
2
            ttl,
444
2
            futile,
445
2
        };
446
2
        Ok(())
447
2
    }
448

            
449
    /// Fetches a consensus from an upstream authority.
450
    // TODO DIRMIRROR: Add logging.
451
    #[allow(clippy::string_slice)] // TODO
452
1
    async fn fetch_consensus(
453
1
        &self,
454
1
        data: &mut ConsensusBoundData<T>,
455
1
        endpoint: &DownloadAuthority,
456
1
        now: Timestamp,
457
1
    ) -> Result<(), AuthorityRequestError> {
458
        // Obtain the consensus.
459
1
        let (raw, consensus) = self
460
1
            .send_request(endpoint, ConsensusRequest::new(T::flavor()))
461
1
            .await?;
462
1
        let mut consensus = consensus
463
1
            .into_iter()
464
1
            .map(|(doc, start, end)| (raw[start..end].to_owned(), doc))
465
1
            .collect::<VecDeque<_>>();
466

            
467
        // Check for the correct number of results.
468
1
        if consensus.len() != 1 {
469
            return Err(AuthorityRequestError::Response(
470
                "invalid number of consensus?",
471
            ));
472
1
        }
473

            
474
        // expect is fine because we checked the length for one above.
475
1
        let (raw, consensus) = consensus.pop_front().expect("pop_front");
476

            
477
1
        let ttl = now + UNVERIFIED_TTL;
478

            
479
        // And store it.
480
1
        *data = ConsensusBoundData::Unverified {
481
1
            consensus,
482
1
            raw,
483
1
            ttl,
484
1
        };
485

            
486
1
        Ok(())
487
1
    }
488

            
489
    /// Fetches, validates, and stores authority certificates.
490
    #[allow(clippy::string_slice)] // TODO
491
1
    async fn auth_certs(
492
1
        &self,
493
1
        pool: &Pool<SqliteConnectionManager>,
494
1
        data: &mut ConsensusBoundData<T>,
495
1
        endpoint: &DownloadAuthority,
496
1
        now: Timestamp,
497
1
    ) -> Result<(), OperationError> {
498
1
        let certs_already = db::read_tx(pool, |tx| self.certs_already(tx, now))??;
499

            
500
        // Obtain the missing authority certificate identifiers.
501
1
        let missing = match data {
502
1
            ConsensusBoundData::Unverified { consensus, .. } => {
503
1
                match consensus.can_verify(self.authorities.v3idents(), &certs_already) {
504
1
                    Err(ConsensusVerifiabilityError::MissingAuthCerts { missing, .. }) => missing,
505
                    _ => return Err(OperationError::Bug(internal!("we have all auth certs"))),
506
                }
507
            }
508
            _ => return Err(OperationError::Bug(internal!("data is not unverified"))),
509
        };
510

            
511
        // Compose the request.
512
1
        let mut requ = AuthCertRequest::new();
513
4
        for kp in missing.iter().copied() {
514
4
            requ.push(kp);
515
4
        }
516

            
517
        // Fire it off.
518
1
        let (resp, certs) = self
519
1
            .send_request::<_, AuthCertUnverified>(endpoint, requ)
520
1
            .await?;
521

            
522
        // Verify each certificate.  Invalid certificates and other problems get
523
        // logged and filtered out, with the result being then inserted into
524
        // the database.
525
1
        let certs = certs
526
1
            .into_iter()
527
4
            .filter_map(|(unverified, start, end)| {
528
4
                let kp = unverified.inspect_unverified().0.key_ids();
529

            
530
                // Skip certificates we did not asked for.
531
                //
532
                // Not much of an issue because certificate verification will
533
                // usually fail anyways, except for this weird edge-case where we
534
                // actually have that id fingerprint in the v3idents.
535
4
                if !missing.contains(&kp) {
536
                    debug!("authority returned certificate we did not asked for: {kp:?}");
537
                    return None;
538
4
                }
539

            
540
4
                let verified = match unverified.verify(self.authorities.v3idents()) {
541
4
                    Ok(c) => c,
542
                    Err(e) => {
543
                        debug!("received invalid auth cert ({kp:?}): {e}");
544
                        return None;
545
                    }
546
                };
547

            
548
4
                let timely = match self
549
4
                    .tolerance
550
4
                    .extend_tolerance(verified)
551
4
                    .if_valid_at(&now.into())
552
                {
553
4
                    Ok(c) => c,
554
                    Err(e) => {
555
                        debug!("received non-timely auth cert ({kp:?}): {e}");
556
                        return None;
557
                    }
558
                };
559

            
560
4
                Some((timely, &resp[start..end]))
561
4
            })
562
1
            .collect::<Vec<_>>();
563

            
564
        // When we have reached this, it means that this call made no progress,
565
        // i.e. the authority only returned certificates we were not interested
566
        // in.
567
1
        if certs.is_empty() {
568
            Err(AuthorityRequestError::NoRequestedDocumentsReturned)?;
569
1
        }
570

            
571
        // Finally, insert them all into the database.
572
1
        db::rw_tx(pool, |tx| {
573
4
            for (cert, data) in certs {
574
4
                AuthCertMeta::insert(tx, self.encodings.iter().copied(), &cert, data)?;
575
            }
576
1
            Ok::<_, DatabaseError>(())
577
1
        })??;
578

            
579
1
        Ok(())
580
1
    }
581

            
582
    /// Verifies a consensus and inserts it into the database.
583
1
    fn store_consensus(
584
1
        &self,
585
1
        pool: &Pool<SqliteConnectionManager>,
586
1
        data: &mut ConsensusBoundData<T>,
587
1
        now: Timestamp,
588
1
    ) -> Result<(), OperationError> {
589
        // It is fine to replace data with ConsensusBoundData::None because in
590
        // both cases, the following state of this will always be LoadConsensus
591
        // or FetchConsensus, both of them no longer requiring the previous data.
592
        //
593
        // Yes, we want to explicitly discard this in the case of a recoverable
594
        // error in order to retry again by fetching a new consensus from a
595
        // different authority.
596
1
        let (unverified, raw) = match mem::replace(data, ConsensusBoundData::None) {
597
1
            ConsensusBoundData::Unverified { consensus, raw, .. } => (consensus, raw),
598
            _ => return Err(OperationError::Bug(internal!("data is not unverified"))),
599
        };
600

            
601
        // TODO DIRMIRROR: Somewhere in here, we should generate the consensus
602
        // diffs.  However, this is a CPU intensive task and needs further
603
        // coordination with the orport developers.  See arti#2706
604

            
605
1
        db::rw_tx(pool, |tx| {
606
1
            let certs_already = self.certs_already(tx, now)?;
607
1
            let (verified, sigs) = self.verify_consensus(unverified, &certs_already, now)?;
608
1
            ConsensusMeta::<T>::insert(
609
1
                tx,
610
1
                self.encodings.iter().copied(),
611
1
                (&verified, &sigs),
612
1
                &raw,
613
            )?;
614
1
            Ok::<_, OperationError>(())
615
1
        })?
616
1
    }
617

            
618
    /// Hibernates for the remaining ttl of the consensus.
619
    async fn hibernate(
620
        &self,
621
        data: &mut ConsensusBoundData<T>,
622
        now: Timestamp,
623
    ) -> Result<(), OperationError> {
624
        match data {
625
            ConsensusBoundData::None => {
626
                return Err(internal!("hibernating without a consensus?").into());
627
            }
628
            // TODO DIRMIRROR: Should the Unverified case be an error or a
629
            // success? In other words: Should we exclude the current authority
630
            // from the set of the next authorities to try?
631
            ConsensusBoundData::Verified { ttl, .. }
632
            | ConsensusBoundData::Unverified { ttl, .. } => {
633
                let timeout = *ttl - now;
634
                debug!("hibernating for {}s", timeout.as_secs());
635
                tokio::time::sleep(timeout).await;
636
            }
637
        }
638

            
639
        Ok(())
640
    }
641

            
642
    /// Convenience wrapper around [`tor_dirclient::send_request()`].
643
    ///
644
    /// It opens a TCP connection, performs the request, and parses the result.
645
    ///
646
    /// Returns the raw response alongside the output of
647
    /// [`parse2::parse_netdoc_multiple_sophisticated()`] with the results being
648
    /// filtered.
649
    ///
650
    /// Invalid documents are ignored but a warning is logged.
651
    ///
652
    /// The output is required because we need the raw document alongside the
653
    /// offsets to have the actual data we will insert into the database later
654
    /// on.
655
2
    async fn send_request<R: Requestable, D: NetdocParseable>(
656
2
        &self,
657
2
        endpoint: &DownloadAuthority,
658
2
        requ: R,
659
2
    ) -> Result<(String, Vec<(D, usize, usize)>), AuthorityRequestError> {
660
        // The check is required to not let Tokio panic.
661
2
        if endpoint.is_empty() {
662
            return Err(AuthorityRequestError::Bug(internal!("empty endpoint?")));
663
2
        }
664

            
665
        // Open the TCP connection.
666
2
        let mut stream = TcpStream::connect(endpoint)
667
2
            .await
668
2
            .map_err(AuthorityRequestError::TcpConnect)?
669
2
            .compat();
670

            
671
        // Perform the request and map the result nicely.
672
2
        let resp = tor_dirclient::send_request(&self.rt, &requ, &mut stream, None)
673
2
            .await
674
2
            .map(|resp| resp.output_string().map(|resp| resp.to_owned()));
675

            
676
        // We can immediately drop the connection now, no need to occupy even
677
        // more resources from the authority.  Doing so is fine, it is HTTP/1.0
678
        // and there is no connection reuse anyways.
679
2
        drop(stream);
680

            
681
        // Returning all request failed errors is okay; they all imply that
682
        // retrying from a different authority is fine.
683
        // TODO MSRV: If possible, use Result::flatten once MSRV 1.89.
684
2
        let resp = match resp {
685
2
            Ok(Ok(r)) => Ok(r),
686
            Ok(Err(e)) => Err(e),
687
            Err(tor_dirclient::Error::RequestFailed(e)) => Err(e),
688
            Err(e) => {
689
                return Err(AuthorityRequestError::Bug(internal!(
690
                    "unhandled dirclient error: {e}"
691
                )));
692
            }
693
        }?;
694

            
695
        // Parse the response.
696
2
        let parsed = parse2::parse_netdoc_multiple_sophisticated(&ParseInput::new(&resp, ""))?
697
2
            .into_iter()
698
5
            .filter_map(|(res, start, end)| match res {
699
5
                Ok(doc) => Some((doc, start, end)),
700
                Err(e) => {
701
                    debug!("ignoring invalid netdoc: {e}");
702
                    None
703
                }
704
5
            })
705
2
            .collect();
706

            
707
2
        Ok((resp, parsed))
708
2
    }
709

            
710
    /// Queries and validates the most recent consensus from the database.
711
    ///
712
    /// Effectively a wrapper around [`StaticEngine::verify_consensus()`] with
713
    /// the data obtained from the database.
714
4
    fn recent_consensus(
715
4
        &self,
716
4
        tx: &Transaction<'_>,
717
4
        now: Timestamp,
718
4
    ) -> Result<Option<ConsensusMeta<T>>, DatabaseError> {
719
4
        let meta = match ConsensusMeta::<T>::query(tx)?.as_slice() {
720
4
            &[front] | &[front, ..] => front,
721
            &[] => return Ok(None),
722
        };
723
4
        let raw = meta.data(tx)?;
724

            
725
4
        let unverified = match parse2::parse_netdoc::<T>(&ParseInput::new(&raw, "<database>")) {
726
4
            Ok(unverified) => unverified,
727
            Err(e) => {
728
                // TODO DIRMIRROR: Perhaps we should remove it?
729
                debug!("unparseable consensus in database: {meta:?} {e}");
730
                return Ok(None);
731
            }
732
        };
733

            
734
4
        let certs_already = self.certs_already(tx, now)?;
735

            
736
4
        match self.verify_consensus(unverified, &certs_already, now) {
737
3
            Ok(_) => Ok(Some(meta)),
738
1
            Err(e) => {
739
                // TODO DIRMIRROR: Perhaps we should remove it?
740
1
                debug!("ignoring invalid consensus in database: {meta:?} {e}");
741
1
                Ok(None)
742
            }
743
        }
744
4
    }
745

            
746
    /// Parses and verifies a raw consensus in a database agnostic fashion.
747
5
    fn verify_consensus(
748
5
        &self,
749
5
        unverified: T,
750
5
        certs_already: &[AuthCert],
751
5
        now: Timestamp,
752
5
    ) -> Result<(T::Body, T::Signatures), VerifyFailed> {
753
5
        let sigs = unverified.sigs().clone();
754

            
755
5
        let verified = unverified.verify(self.authorities.v3idents(), certs_already)?;
756

            
757
4
        self.tolerance
758
4
            .extend_tolerance(verified)
759
4
            .if_valid_at(&now.into())
760
4
            .map(|body| (body, sigs))
761
4
            .map_err(|e| e.into())
762
5
    }
763

            
764
    /// Loads all authority certificates from the database into a [`Vec<AuthCert>`].
765
    ///
766
    /// Performance wise, it is quite inefficient as it parses and verifies all
767
    /// certificates over again.  However, this should be fine as the N for
768
    /// directory authorities is usually very small.
769
13
    fn certs_already(
770
13
        &self,
771
13
        tx: &Transaction<'_>,
772
13
        now: Timestamp,
773
13
    ) -> Result<Vec<AuthCert>, DatabaseError> {
774
13
        let raw_certs = AuthCertMeta::query(tx)?
775
13
            .into_iter()
776
44
            .map(|meta| Ok::<_, DatabaseError>((meta, meta.data(tx)?)))
777
13
            .collect::<Result<Vec<_>, _>>()?;
778

            
779
13
        let mut certs_already = Vec::new();
780
44
        for (meta, raw) in raw_certs {
781
            // Only used for debugging.
782
            // TODO DIRMIRROR: Put this into span with tracing.
783
44
            let key_ids = AuthCertKeyIds {
784
44
                id_fingerprint: meta.kp_auth_id_rsa_sha1.0.into(),
785
44
                sk_fingerprint: meta.kp_auth_sign_rsa_sha1.0.into(),
786
44
            };
787

            
788
44
            let unverified = match parse2::parse_netdoc::<AuthCertUnverified>(&ParseInput::new(
789
44
                &raw,
790
44
                "<database>",
791
44
            )) {
792
44
                Ok(c) => c,
793
                Err(e) => {
794
                    // TODO DIRMIRROR: Perhaps we should remove it?
795
                    debug!("unparseable auth cert ({key_ids:?}) in database: {e}");
796
                    continue;
797
                }
798
            };
799

            
800
44
            let verified = match unverified.verify(self.authorities.v3idents()) {
801
40
                Ok(c) => c,
802
4
                Err(e) => {
803
4
                    debug!("ignoring invalid auth cert ({key_ids:?}): {e}");
804
4
                    continue;
805
                }
806
            };
807

            
808
40
            let timely = match self
809
40
                .tolerance
810
40
                .extend_tolerance(verified)
811
40
                .if_valid_at(&now.into())
812
            {
813
32
                Ok(c) => c,
814
8
                Err(e) => {
815
8
                    debug!("ignoring non-timely auth cert ({key_ids:?}): {e}");
816
8
                    continue;
817
                }
818
            };
819
32
            certs_already.push(timely);
820
        }
821
13
        Ok(certs_already)
822
13
    }
823
}
824

            
825
/// Top-level collections storing which descriptors are futile.
826
#[derive(Debug, Clone, Default)]
827
struct FutileDescriptors {
828
    /// The futile router descriptors.
829
    routers: FutileDescriptorSet<Sha1>,
830

            
831
    /// The futile extra-infos.
832
    extras: FutileDescriptorSet<Sha1>,
833

            
834
    /// The futile micro descriptors.
835
    micros: FutileDescriptorSet<Sha256>,
836
}
837

            
838
/// A generic struct storing which descriptors are futile for which reason.
839
#[derive(Educe)]
840
#[educe(Debug, Clone, Default)]
841
struct FutileDescriptorSet<T: Debug + Clone + Copy + Eq + Hash> {
842
    /// The descriptors that are not found on a given endpoint.
843
    not_found: HashSet<(T, Box<DownloadAuthority>)>,
844
    /// The descriptor that are outright refused.
845
    refused: HashSet<T>,
846
}
847

            
848
impl<T: Debug + Clone + Copy + Eq + Hash> FutileDescriptorSet<T> {
849
    /// Returns an iterator over the digest that are not found.
850
    fn not_found(&self, endpoint: &DownloadAuthority) -> impl Iterator<Item = T> {
851
        self.not_found
852
            .iter()
853
            .filter(|(_, this_endpoint)| **this_endpoint == *endpoint)
854
            .map(|(d, _)| *d)
855
    }
856

            
857
    /// Returns an iterator over the digest that are refused.
858
    fn refused(&self) -> impl Iterator<Item = T> {
859
        self.refused.iter().copied()
860
    }
861

            
862
    /// Chaining of `.not_found()` and `.refused()`.
863
    fn excluded(&self, endpoint: &DownloadAuthority) -> impl Iterator<Item = T> {
864
        self.not_found(endpoint).chain(self.refused())
865
    }
866

            
867
    /// Marks a digest in combination with an authority as not found.
868
    fn set_not_found(&mut self, digest: T, endpoint: &DownloadAuthority) {
869
        self.not_found.insert((digest, endpoint.into()));
870
    }
871

            
872
    /// Marks a digest as refused.
873
    fn set_refused(&mut self, digest: T) {
874
        self.refused.insert(digest);
875
    }
876
}
877

            
878
#[cfg(test)]
879
mod test {
880
    // @@ begin test lint list maintained by maint/add_warning @@
881
    #![allow(clippy::bool_assert_comparison)]
882
    #![allow(clippy::clone_on_copy)]
883
    #![allow(clippy::dbg_macro)]
884
    #![allow(clippy::mixed_attributes_style)]
885
    #![allow(clippy::print_stderr)]
886
    #![allow(clippy::print_stdout)]
887
    #![allow(clippy::single_char_pattern)]
888
    #![allow(clippy::unwrap_used)]
889
    #![allow(clippy::unchecked_time_subtraction)]
890
    #![allow(clippy::useless_vec)]
891
    #![allow(clippy::needless_pass_by_value)]
892
    #![allow(clippy::string_slice)] // See arti#2571
893
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
894

            
895
    use std::{collections::HashSet, iter};
896

            
897
    use rusqlite::params;
898
    use strum::IntoEnumIterator;
899
    use tokio::{
900
        io::{AsyncReadExt, AsyncWriteExt},
901
        net::TcpListener,
902
    };
903
    use tor_basic_utils::test_rng::testing_rng;
904

            
905
    use crate::{database::sql, testdata2};
906

            
907
    use super::*;
908

            
909
    type Plain = tor_netdoc::doc::netstatus::plain::NetworkStatusUnverified;
910
    type Md = tor_netdoc::doc::netstatus::md::NetworkStatusUnverified;
911

            
912
    /// Returns a [`StaticEngine`] for testing.
913
    fn static_engine<T>() -> StaticEngine<T> {
914
        StaticEngine {
915
            authorities: testdata2::current_auth_cert_contacts(),
916
            tolerance: DirTolerance::default(),
917
            rt: PreferredRuntime::current().unwrap(),
918
            encodings: ContentEncoding::iter().collect(),
919
            _phantom: Default::default(),
920
        }
921
    }
922

            
923
    /// Tests whether the load consensus state computes missing descriptors
924
    /// properly.
925
    ///
926
    /// For this, the test removes a present router descriptor from the storage
927
    /// to verify that it is detected as missing and added to the download
928
    /// queue.
929
    #[tokio::test]
930
    async fn state_load_consensus() {
931
        let pool = testdata2::test_db();
932
        let mut data = ConsensusBoundData::<Plain>::None;
933
        let engine = static_engine();
934

            
935
        let time: Timestamp = testdata2::valid_system_time().into();
936
        let fresh_until: Timestamp = testdata2::current_consensus_ns()
937
            .0
938
            .preamble
939
            .lifetime
940
            .fresh_until
941
            .0
942
            .into();
943
        let valid_until: Timestamp = testdata2::current_consensus_ns()
944
            .0
945
            .preamble
946
            .lifetime
947
            .valid_until
948
            .0
949
            .into();
950
        // This is the middle of valid_until and fresh_until.
951
        let fresh_until_half = fresh_until + ((valid_until - fresh_until) / 2);
952

            
953
        // Remove a single router descriptor from our storage to see whether it
954
        // appears in the download queue as expected.
955
        let relay_to_remove = &testdata2::current_consensus_ns().0.routers[0];
956
        let relay_to_remove = db::Sha1::from(*relay_to_remove.doc_digest());
957
        pool.get()
958
            .unwrap()
959
            .execute(
960
                sql!("DELETE FROM descriptor WHERE sha1 = ?1"),
961
                params![relay_to_remove],
962
            )
963
            .unwrap();
964

            
965
        engine
966
            .load_consensus(&pool, &mut data, time, &mut testing_rng())
967
            .unwrap();
968

            
969
        // El-cheapo assert_eq due to lack of PartialEq for tor-netdoc poc.
970
        match data {
971
            ConsensusBoundData::Verified { consensus, ttl, .. } => {
972
                // If everything worked properly, then the queue should only
973
                // contain the relay we removed, because that is missing now.
974
                db::read_tx(&pool, |tx| {
975
                    assert_eq!(
976
                        consensus.missing_routers(tx, iter::empty(), None).unwrap(),
977
                        HashSet::from([relay_to_remove])
978
                    );
979
                    assert!(
980
                        consensus
981
                            .missing_extras(tx, iter::empty(), None)
982
                            .unwrap()
983
                            .is_empty()
984
                    );
985
                    assert!(
986
                        consensus
987
                            .missing_micros(tx, iter::empty(), None)
988
                            .unwrap()
989
                            .is_empty()
990
                    );
991
                })
992
                .unwrap();
993
                assert!(ttl >= fresh_until);
994
                assert!(ttl <= fresh_until_half);
995
            }
996
            _ => panic!("data is not verified"),
997
        }
998
    }
999

            
    /// Tests whether the fetch consensus state properly fetches a consensus
    /// and keeps it in memory as unverified.
    ///
    /// For this, we spawn a tokio task simulating a web server which responds
    /// with a consensus.
    #[tokio::test]
    async fn state_fetch_consensus() {
        let pool = testdata2::test_db();
        let mut data = ConsensusBoundData::<Plain>::None;
        let engine = static_engine();
        let now = Timestamp::from(testdata2::invalid_system_time());
        let state = db::read_tx(&pool, |tx| engine.determine_state(tx, &data, now))
            .unwrap()
            .unwrap();
        assert_eq!(state, State::FetchConsensus);
        let server = TcpListener::bind("[::1]:0").await.unwrap();
        let saddr = server.local_addr().unwrap();
        tokio::spawn(async move {
            let (mut stream, _) = server.accept().await.unwrap();
            let mut buf = vec![0; 1024];
            let _ = stream.read(&mut buf).await.unwrap();
            let consensus = testdata2::current_consensus_ns().2;
            let resp = format!(
                "HTTP/1.0 200 OK\r\nContent-Encoding: identity\r\nContent-Length: {}\r\n\r\n{consensus}",
                consensus.len()
            );
            stream.write_all(resp.as_bytes()).await.unwrap();
        });
        engine
            .fetch_consensus(&mut data, &[saddr], now)
            .await
            .unwrap();
        match data {
            ConsensusBoundData::Unverified { raw, ttl, .. } => {
                assert_eq!(raw, testdata2::current_consensus_ns().2);
                assert_eq!(ttl, now + UNVERIFIED_TTL);
            }
            _ => panic!("data is not unverified"),
        }
    }
    /// Tests the download, verification, and insertion of authority certificates.
    ///
    /// For this, it starts by removing an existing one from the test database
    /// to see it getting re-downloaded, re-verified, and re-inserted again.
    #[tokio::test]
    async fn state_auth_certs() {
        let pool = testdata2::test_db();
        let mut data = ConsensusBoundData::<Plain>::Unverified {
            consensus: parse2::parse_netdoc(&ParseInput::new(
                testdata2::current_consensus_ns().2,
                "",
            ))
            .unwrap(),
            raw: testdata2::current_consensus_ns().2.to_owned(),
            ttl: Timestamp::from(testdata2::valid_system_time()) + UNVERIFIED_TTL,
        };
        let mut engine = static_engine();
        // We want to download authority certificates; for this, remove
        // one of them from the database.
        pool.get()
            .unwrap()
            .execute(
                sql!(
                    "
                    DELETE FROM authority_key_certificate
                    WHERE :kp_auth_id_rsa_sha1 = ?1
                    "
                ),
                params![db::Sha1::from(
                    testdata2::current_auth_cert_ids()[0].to_bytes()
                )],
            )
            .unwrap();
        assert_eq!(
            db::read_tx(&pool, |tx| engine.determine_state(
                tx,
                &data,
                testdata2::valid_system_time().into()
            ))
            .unwrap()
            .unwrap(),
            State::AuthCerts
        );
        let server = TcpListener::bind("[::1]:0").await.unwrap();
        let saddr = server.local_addr().unwrap();
        tokio::spawn(async move {
            let mut buf = [0; 1024];
            let (mut stream, _) = server.accept().await.unwrap();
            let _ = stream.read(&mut buf).await.unwrap();
            let authcerts = testdata2::current_auth_certs()
                .into_iter()
                .map(|x| x.1)
                .collect::<String>();
            stream.write_all(format!(
                "HTTP/1.0 200 OK\r\nContent-Encoding: identity\r\nContent-Length: {}\r\n\r\n{authcerts}",
                authcerts.len()
            ).as_bytes()).await.unwrap();
        });
        // Fetch all authcerts.
        engine
            .auth_certs(
                &pool,
                &mut data,
                &[saddr],
                testdata2::valid_system_time().into(),
            )
            .await
            .unwrap();
        // Check whether we are done with all authcerts.
        assert_eq!(
            db::read_tx(&pool, |tx| engine.determine_state(
                tx,
                &data,
                testdata2::valid_system_time().into(),
            ))
            .unwrap()
            .unwrap(),
            State::StoreConsensus
        );
        // Check whether we now have all auth certs in the database.
        let certs = db::read_tx(&pool, |tx| {
            engine.certs_already(tx, testdata2::valid_system_time().into())
        })
        .unwrap()
        .unwrap();
        let got = certs
            .into_iter()
            .map(|c| c.dir_identity_key.to_rsa_identity())
            .collect::<HashSet<_>>();
        let expect = engine.authorities.v3idents().iter().copied().collect();
        assert_eq!(got, expect);
        // Check an edge case of not having enough trusted signers.
        engine.authorities = AuthorityContacts::builder().build().unwrap();
        let state = db::read_tx(&pool, |tx| {
            engine.determine_state(tx, &data, testdata2::valid_system_time().into())
        })
        .unwrap()
        .unwrap();
        assert_eq!(state, State::Hibernate);
    }
    /// Checks whether we can probably load and parse all certificates from
    /// the database.
    #[tokio::test]
    async fn certs_already() {
        let pool = testdata2::test_db();
        let engine = static_engine::<Plain>();
        db::read_tx(&pool, |tx| {
            // With a correct system time, this should align with all authority
            // fingerprints we have.
            let certs_already = engine
                .certs_already(tx, testdata2::valid_system_time().into())
                .unwrap();
            assert_eq!(certs_already.len(), engine.authorities.v3idents().len());
            assert!(certs_already.iter().all(|c| {
                engine
                    .authorities
                    .v3idents()
                    .contains(&c.dir_identity_key.to_rsa_identity())
            }));
            // With an invalid system time, it should be empty.
            let certs_already = engine
                .certs_already(tx, testdata2::invalid_system_time().into())
                .unwrap();
            assert!(certs_already.is_empty());
        })
        .unwrap();
    }
    /// Ensures that an unverified consensus gets properly inserted into the
    /// database.
    #[tokio::test]
    async fn state_store_consensus() {
        let pool = testdata2::test_db();
        let unverified: Plain =
            parse2::parse_netdoc(&ParseInput::new(testdata2::current_consensus_ns().2, ""))
                .unwrap();
        let engine = static_engine();
        let now = Timestamp::from(testdata2::valid_system_time());
        let mut data = ConsensusBoundData::<Plain>::Unverified {
            consensus: unverified.clone(),
            raw: testdata2::current_consensus_ns().2.to_string(),
            ttl: now + UNVERIFIED_TTL,
        };
        // Clean all consensus related tables.
        pool.get()
            .unwrap()
            .execute_batch(sql!(
                "
                DELETE FROM consensus_descriptor_member;
                DELETE FROM consensus_authority_voter;
                DELETE FROM consensus;
                "
            ))
            .unwrap();
        // Execute the StoreConsensus state.
        let state = db::read_tx(&pool, |tx| engine.determine_state(tx, &data, now))
            .unwrap()
            .unwrap();
        assert_eq!(state, State::StoreConsensus);
        engine.store_consensus(&pool, &mut data, now).unwrap();
        assert!(matches!(&data, ConsensusBoundData::None));
        // Verify that it got inserted.
        let state = db::read_tx(&pool, |tx| engine.determine_state(tx, &data, now))
            .unwrap()
            .unwrap();
        assert_eq!(state, State::LoadConsensus);
        engine
            .load_consensus(&pool, &mut data, now, &mut testing_rng())
            .unwrap();
        assert!(matches!(data, ConsensusBoundData::Verified { .. }));
    }
}