1
//! Arti consensus method plugin for C Tor
2
//!
3
//! Implements `authority-plugin.md` pursuant to `doc/dev/notes/dirauth-sketch.md`.
4
//!
5
//! This is the actual implementation.
6

            
7
use std::collections::HashSet;
8
use std::time::SystemTime;
9

            
10
use anyhow::{Context as _, anyhow};
11
use derive_deftly::{Deftly, define_derive_deftly};
12
use digest::Digest as _;
13
use educe::Educe;
14

            
15
use tor_checkable::TimeBound as _;
16
use tor_dirauth::consensus;
17
use tor_error::{Bug, ErrorReport as _, HasKind as _};
18
use tor_llcrypto::d::Sha256;
19
use tor_netdoc::{
20
    doc::netstatus::ConsensusMethod,
21
    doc::routerdesc::{RouterDesc, RouterDescUnverified},
22
    encode::encode_netdoc_unsigned,
23
    parse2::{self, NetdocParseable},
24
    types::{Base64Fingerprint, Ed25519Public, FixedB64, Iso8601TimeNoSp},
25
};
26

            
27
#[macro_use]
28
mod utils;
29
mod compute_mds;
30

            
31
use utils::{CTorAnnotated, FilenameOrStdio, Writing};
32

            
33
/// Options and arguments to plugin invocation
34
#[derive(Debug, clap::Parser)]
35
#[command(after_help =
36
//
37
"For details of semantics, exit status, input/output formats, etc., see the spec:
38
   arti-authority-plugin dump-spec
39
Latest version:
40
   https://gitlab.torproject.org/tpo/core/arti/-/blob/main/crates/arti-dirauth/authority-plugin.md
41
"
42
)]
43
struct CliArgs {
44
    /// Operation verb and its arguments
45
    #[command(subcommand)]
46
    op: CliOperation,
47
}
48

            
49
/// Operation verb and its arguments
50
#[derive(Debug, Clone, clap::Subcommand)]
51
enum CliOperation {
52
    /// `list-methods`, "mode 1"
53
    ListMethods {
54
        /// Output file
55
        #[arg(short = 'o')]
56
        output: FilenameOrStdio,
57
    },
58

            
59
    /// `compute-mds`, "mode 2"
60
    ComputeMds {
61
        /// Nominal time (for routerdesc verification)
62
        #[arg(short = 't')]
63
        nominal_time: Iso8601TimeNoSp,
64

            
65
        /// Microdescriptors out
66
        #[arg(short = 'i')]
67
        input: Vec<FilenameOrStdio>,
68

            
69
        /// Microdescriptors out
70
        #[arg(long)]
71
        mds_out: FilenameOrStdio,
72

            
73
        /// Metadata out
74
        #[arg(long)]
75
        meta_out: FilenameOrStdio,
76
    },
77

            
78
    /// Print the specification to stdout, in markdown format
79
    //
80
    // This is to save us from having to document everything again here.
81
    // Instead, this can be regarded as part of the program's help output.
82
    // Our help output has a link to the rendered version in gitlab
83
    // (`after_help` attribute on `CliArgs`)
84
    DumpSpec {},
85
}
86

            
87
/// Top-level error - program exits with this, or `Ok(())`
88
#[derive(Debug, thiserror::Error)]
89
enum CliError {
90
    /// Invalid operation or usage
91
    #[error("invalid operation or usage")]
92
    InvalidInputs(#[source] anyhow::Error),
93

            
94
    /// Unsupported consensus method
95
    #[error("fall back to C Tor")]
96
    #[allow(dead_code)] // TODO DIRAUTH
97
    UnsupportedConsensusMethod(#[from] consensus::UnsupportedConsensusMethod),
98

            
99
    /// Operational error
100
    #[error("failed")]
101
    OperationalError(#[source] anyhow::Error),
102
}
103

            
104
//==================== implementations ====================
105

            
106
/// Actual implementation of the plugin's invocations
107
///
108
/// Split off for ease of testing.
109
fn plugin_impl(args: CliArgs) -> Result<(), CliError> {
110
    match args.op {
111
        CliOperation::ListMethods { output } => output.write(|w| {
112
            for m in consensus::SupportedConsensusMethod::iter_all() {
113
                writeln!(w, "{m}")?;
114
            }
115
            Ok(())
116
        }),
117
        CliOperation::ComputeMds {
118
            nominal_time,
119
            input,
120
            mds_out,
121
            meta_out,
122
        } => {
123
            let mut mds_out = mds_out.start_writing()?;
124
            let mut meta_out = meta_out.start_writing()?;
125
            let mut processor =
126
                compute_mds::Processor::new(*nominal_time, &mut mds_out, &mut meta_out);
127
            for i in input {
128
                let i = i.start_reading()?;
129
                let desc = i.description().to_owned();
130
                processor.process_input(parse2::ParseInput::new(
131
                    //
132
                    &i.read_entire_string()?,
133
                    &desc,
134
                ))?;
135
            }
136
            mds_out.finish()?;
137
            meta_out.finish()?;
138
            Ok(())
139
        }
140

            
141
        CliOperation::DumpSpec {} => {
142
            print!("{}", include_str!("../authority-plugin.md"));
143
            Ok(())
144
        }
145
    }
146
}
147

            
148
/// Entrypoint for the Arti-in-C-Tor consensus method plugin
149
pub fn plugin_main() {
150
    match (|| {
151
        let args = <CliArgs as clap::Parser>::try_parse()
152
            .context("invalid arguments")
153
            .map_err(CliError::InvalidInputs)?;
154

            
155
        plugin_impl(args)
156
    })() {
157
        Ok(()) => {}
158
        Err(e) => {
159
            eprintln!("arti-authority-plugin: error: {}", e.report());
160
            std::process::exit(i32::from(e.exit_status()));
161
        }
162
    }
163
}
164

            
165
impl CliError {
166
    /// Exit status corresponding to this error, as per `authority-plugin.md`
167
    ///
168
    /// Returns `u8` because that's what Unix processes can exit,
169
    /// `std::process:exit`'s `i32` argument notwithstanding.
170
    fn exit_status(&self) -> u8 {
171
        use CliError as E;
172
        match self {
173
            E::InvalidInputs { .. } => 8,
174
            E::UnsupportedConsensusMethod { .. } => 10,
175
            E::OperationalError { .. } => 32,
176
        }
177
    }
178
}
179

            
180
impl From<Bug> for CliError {
181
    fn from(bug: Bug) -> Self {
182
        CliError::OperationalError(bug.into())
183
    }
184
}