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 anyhow::Context as _;
8

            
9
use tor_dirauth::consensus;
10
use tor_error::ErrorReport as _;
11

            
12
mod utils;
13
use utils::FilenameOrStdio;
14

            
15
/// Options and arguments to plugin invocation
16
#[derive(Debug, clap::Parser)]
17
#[command(after_help =
18
//
19
"For details of semantics, exit status, input/output formats, etc., see the spec:
20
   arti-authority-plugin dump-spec
21
Latest version:
22
   https://gitlab.torproject.org/tpo/core/arti/-/blob/main/crates/arti-dirauth/authority-plugin.md
23
"
24
)]
25
struct CliArgs {
26
    /// Operation verb and its arguments
27
    #[command(subcommand)]
28
    op: CliOperation,
29
}
30

            
31
/// Operation verb and its arguments
32
#[derive(Debug, Clone, clap::Subcommand)]
33
enum CliOperation {
34
    /// `list-methods`, "mode 1"
35
    ListMethods {
36
        /// Output file
37
        #[arg(short = 'o')]
38
        output: FilenameOrStdio,
39
    },
40

            
41
    /// Print the specification to stdout, in markdown format
42
    //
43
    // This is to save us from having to document everything again here.
44
    // Instead, this can be regarded as part of the program's help output.
45
    // Our help output has a link to the rendered version in gitlab
46
    // (`after_help` attribute on `CliArgs`)
47
    DumpSpec {},
48
}
49

            
50
/// Top-level error - program exits with this, or `Ok(())`
51
#[derive(Debug, thiserror::Error)]
52
enum CliError {
53
    /// Invalid operation or usage
54
    #[error("invalid operation or usage")]
55
    InvalidInputs(#[source] anyhow::Error),
56

            
57
    /// Unsupported consensus method
58
    #[error("fall back to C Tor")]
59
    #[allow(dead_code)] // TODO DIRAUTH
60
    UnsupportedConsensusMethod(#[from] consensus::UnsupportedConsensusMethod),
61

            
62
    /// Operational error
63
    #[error("failed")]
64
    OperationalError(#[source] anyhow::Error),
65
}
66

            
67
//==================== implementations ====================
68

            
69
/// Actual implementation of the plugin's invocations
70
///
71
/// Split off for ease of testing.
72
fn plugin_impl(args: CliArgs) -> Result<(), CliError> {
73
    match args.op {
74
        CliOperation::ListMethods { output } => output.write(|w| {
75
            for m in consensus::SupportedConsensusMethod::iter_all() {
76
                writeln!(w, "{m}")?;
77
            }
78
            Ok(())
79
        }),
80

            
81
        CliOperation::DumpSpec {} => {
82
            print!("{}", include_str!("../authority-plugin.md"));
83
            Ok(())
84
        }
85
    }
86
}
87

            
88
/// Entrypoint for the Arti-in-C-Tor consensus method plugin
89
pub fn plugin_main() {
90
    match (|| {
91
        let args = <CliArgs as clap::Parser>::try_parse()
92
            .context("invalid arguments")
93
            .map_err(CliError::InvalidInputs)?;
94

            
95
        plugin_impl(args)
96
    })() {
97
        Ok(()) => {}
98
        Err(e) => {
99
            eprintln!("arti-authority-plugin: error: {}", e.report());
100
            std::process::exit(i32::from(e.exit_status()));
101
        }
102
    }
103
}
104

            
105
impl CliError {
106
    /// Exit status corresponding to this error, as per `authority-plugin.md`
107
    ///
108
    /// Returns `u8` because that's what Unix processes can exit,
109
    /// `std::process:exit`'s `i32` argument notwithstanding.
110
    fn exit_status(&self) -> u8 {
111
        use CliError as E;
112
        match self {
113
            E::InvalidInputs { .. } => 8,
114
            E::UnsupportedConsensusMethod { .. } => 10,
115
            E::OperationalError { .. } => 32,
116
        }
117
    }
118
}