1
//! Miscellaneous types used in configuration
2
//!
3
//! This module contains types that need to be shared across various crates
4
//! and layers, but which don't depend on specific elements of the Tor system.
5

            
6
use std::borrow::Cow;
7
use std::fmt::Debug;
8

            
9
use serde::{Deserialize, Serialize};
10
use strum::{Display, EnumString, IntoStaticStr};
11

            
12
/// Boolean, but with additional `"auto"` option
13
//
14
// This slightly-odd interleaving of derives and attributes stops rustfmt doing a daft thing
15
#[derive(Clone, Copy, Hash, Debug, Default, Ord, PartialOrd, Eq, PartialEq)]
16
#[allow(clippy::exhaustive_enums)] // we will add variants very rarely if ever
17
#[derive(Serialize, Deserialize)]
18
#[serde(try_from = "BoolOrAutoSerde", into = "BoolOrAutoSerde")]
19
pub enum BoolOrAuto {
20
    #[default]
21
    /// Automatic
22
    Auto,
23
    /// Explicitly specified
24
    Explicit(bool),
25
}
26

            
27
impl BoolOrAuto {
28
    /// Returns the explicitly set boolean value, or `None`
29
    ///
30
    /// ```
31
    /// use tor_config::BoolOrAuto;
32
    ///
33
    /// fn calculate_default() -> bool { //...
34
    /// # false }
35
    /// let bool_or_auto: BoolOrAuto = // ...
36
    /// # Default::default();
37
    /// let _: bool = bool_or_auto.as_bool().unwrap_or_else(|| calculate_default());
38
    /// ```
39
14109
    pub fn as_bool(self) -> Option<bool> {
40
14109
        match self {
41
13047
            BoolOrAuto::Auto => None,
42
1062
            BoolOrAuto::Explicit(v) => Some(v),
43
        }
44
14109
    }
45
}
46

            
47
/// How we (de) serialize a [`BoolOrAuto`]
48
#[derive(Serialize, Deserialize)]
49
#[serde(untagged)]
50
enum BoolOrAutoSerde {
51
    /// String (in snake case)
52
    String(Cow<'static, str>),
53
    /// bool
54
    Bool(bool),
55
}
56

            
57
impl From<BoolOrAuto> for BoolOrAutoSerde {
58
8
    fn from(boa: BoolOrAuto) -> BoolOrAutoSerde {
59
        use BoolOrAutoSerde as BoAS;
60
8
        boa.as_bool()
61
8
            .map(BoAS::Bool)
62
12
            .unwrap_or_else(|| BoAS::String("auto".into()))
63
8
    }
64
}
65

            
66
/// Boolean or `"auto"` configuration is invalid
67
#[derive(thiserror::Error, Debug, Clone)]
68
#[non_exhaustive]
69
#[error(r#"Invalid value, expected boolean or "auto""#)]
70
pub struct InvalidBoolOrAuto {}
71

            
72
impl TryFrom<BoolOrAutoSerde> for BoolOrAuto {
73
    type Error = InvalidBoolOrAuto;
74

            
75
958
    fn try_from(pls: BoolOrAutoSerde) -> Result<BoolOrAuto, Self::Error> {
76
        use BoolOrAuto as BoA;
77
        use BoolOrAutoSerde as BoAS;
78
242
        Ok(match pls {
79
712
            BoAS::Bool(v) => BoA::Explicit(v),
80
246
            BoAS::String(s) if s == "false" => BoA::Explicit(false),
81
244
            BoAS::String(s) if s == "true" => BoA::Explicit(true),
82
242
            BoAS::String(s) if s == "auto" => BoA::Auto,
83
4
            _ => return Err(InvalidBoolOrAuto {}),
84
        })
85
958
    }
86
}
87

            
88
/// A macro that implements [`NotAutoValue`] for your type.
89
///
90
/// This macro generates:
91
///   * a [`NotAutoValue`] impl for `ty`
92
///   * a test module with a test that ensures "auto" cannot be deserialized as `ty`
93
///
94
/// ## Example
95
///
96
/// ```rust
97
/// # use tor_config::{impl_not_auto_value, ExplicitOrAuto};
98
/// # use serde::{Serialize, Deserialize};
99
//  #
100
/// #[derive(Serialize, Deserialize)]
101
/// struct Foo;
102
///
103
/// impl_not_auto_value!(Foo);
104
///
105
/// #[derive(Serialize, Deserialize)]
106
/// struct Bar;
107
///
108
/// fn main() {
109
///    let _foo: ExplicitOrAuto<Foo> = ExplicitOrAuto::Auto;
110
///
111
///    // Using a type that does not implement NotAutoValue is an error:
112
///    // let _bar: ExplicitOrAuto<Bar> = ExplicitOrAuto::Auto;
113
/// }
114
/// ```
115
#[macro_export]
116
macro_rules! impl_not_auto_value {
117
    ($ty:ty) => {
118
        $crate::deps::paste! {
119
            impl $crate::NotAutoValue for $ty {}
120

            
121
            #[cfg(test)]
122
            #[allow(non_snake_case)]
123
            mod [<test_not_auto_value_ $ty>] {
124
                #[allow(unused_imports)]
125
                use super::*;
126

            
127
                #[test]
128
38
                fn [<auto_is_not_a_valid_value_for_ $ty>]() {
129
38
                    let res = $crate::deps::serde_value::Value::String(
130
38
                        "auto".into()
131
38
                    ).deserialize_into::<$ty>();
132

            
133
38
                    assert!(
134
38
                        res.is_err(),
135
                        concat!(
136
                            stringify!($ty), " is not a valid NotAutoValue type: ",
137
                            "NotAutoValue types should not be deserializable from \"auto\""
138
                        ),
139
                    );
140
38
                }
141
            }
142
        }
143
    };
144
}
145

            
146
/// A serializable value, or auto.
147
///
148
/// Used for implementing configuration options that can be explicitly initialized
149
/// with a placeholder for their "default" value using the
150
/// [`Auto`](ExplicitOrAuto::Auto) variant.
151
///
152
/// Unlike `#[serde(default)] field: T` or `#[serde(default)] field: Option<T>`,
153
/// fields of this type can be present in the serialized configuration
154
/// without being assigned a concrete value.
155
///
156
/// **Important**: the underlying type must implement [`NotAutoValue`].
157
/// This trait should be implemented using the [`impl_not_auto_value`],
158
/// and only for types that do not serialize to the same value as the
159
/// [`Auto`](ExplicitOrAuto::Auto) variant.
160
///
161
/// ## Example
162
///
163
/// In the following serialized TOML config
164
///
165
/// ```toml
166
///  foo = "auto"
167
/// ```
168
///
169
/// `foo` is set to [`Auto`](ExplicitOrAuto::Auto), which indicates the
170
/// implementation should use a default (but not necessarily [`Default::default`])
171
/// value for the `foo` option.
172
///
173
/// For example, f field `foo` defaults to `13` if feature `bar` is enabled,
174
/// and `9000` otherwise, a configuration with `foo` set to `"auto"` will
175
/// behave in the "default" way regardless of which features are enabled.
176
///
177
/// ```rust,ignore
178
/// struct Foo(usize);
179
///
180
/// impl Default for Foo {
181
///     fn default() -> Foo {
182
///         if cfg!(feature = "bar") {
183
///             Foo(13)
184
///         } else {
185
///             Foo(9000)
186
///         }
187
///     }
188
/// }
189
///
190
/// impl Foo {
191
///     fn from_explicit_or_auto(foo: ExplicitOrAuto<Foo>) -> Self {
192
///         match foo {
193
///             // If Auto, choose a sensible default for foo
194
///             ExplicitOrAuto::Auto => Default::default(),
195
///             ExplicitOrAuto::Foo(foo) => foo,
196
///         }
197
///     }
198
/// }
199
///
200
/// struct Config {
201
///    foo: ExplicitOrAuto<Foo>,
202
/// }
203
/// ```
204
#[derive(Clone, Copy, Hash, Debug, Default, Ord, PartialOrd, Eq, PartialEq)]
205
#[allow(clippy::exhaustive_enums)] // we will add variants very rarely if ever
206
#[derive(Serialize, Deserialize)]
207
pub enum ExplicitOrAuto<T: NotAutoValue> {
208
    /// Automatic
209
    #[default]
210
    #[serde(rename = "auto")]
211
    Auto,
212
    /// Explicitly specified
213
    #[serde(untagged)]
214
    Explicit(T),
215
}
216

            
217
impl<T: NotAutoValue> ExplicitOrAuto<T> {
218
    /// Returns the explicitly set value, or `None`.
219
    ///
220
    /// ```
221
    /// use tor_config::ExplicitOrAuto;
222
    ///
223
    /// fn calculate_default() -> usize { //...
224
    /// # 2 }
225
    /// let explicit_or_auto: ExplicitOrAuto<usize> = // ...
226
    /// # Default::default();
227
    /// let _: usize = explicit_or_auto.into_value().unwrap_or_else(|| calculate_default());
228
    /// ```
229
    pub fn into_value(self) -> Option<T> {
230
        match self {
231
            ExplicitOrAuto::Auto => None,
232
            ExplicitOrAuto::Explicit(v) => Some(v),
233
        }
234
    }
235

            
236
    /// Returns a reference to the explicitly set value, or `None`.
237
    ///
238
    /// Like [`ExplicitOrAuto::into_value`], except it returns a reference to the inner type.
239
    pub fn as_value(&self) -> Option<&T> {
240
        match self {
241
            ExplicitOrAuto::Auto => None,
242
            ExplicitOrAuto::Explicit(v) => Some(v),
243
        }
244
    }
245

            
246
    /// Maps an `ExplicitOrAuto<T>` to an `ExplicitOrAuto<U>` by applying a function to a contained
247
    /// value.
248
96
    pub fn map<U: NotAutoValue>(self, f: impl FnOnce(T) -> U) -> ExplicitOrAuto<U> {
249
96
        match self {
250
2
            Self::Auto => ExplicitOrAuto::Auto,
251
94
            Self::Explicit(x) => ExplicitOrAuto::Explicit(f(x)),
252
        }
253
96
    }
254
}
255

            
256
impl<T> From<T> for ExplicitOrAuto<T>
257
where
258
    T: NotAutoValue,
259
{
260
92
    fn from(x: T) -> Self {
261
92
        Self::Explicit(x)
262
92
    }
263
}
264

            
265
/// A marker trait for types that do not serialize to the same value as [`ExplicitOrAuto::Auto`].
266
///
267
/// **Important**: you should not implement this trait manually.
268
/// Use the [`impl_not_auto_value`] macro instead.
269
///
270
/// This trait should be implemented for types that can be stored in [`ExplicitOrAuto`].
271
pub trait NotAutoValue {}
272

            
273
/// A helper for calling [`impl_not_auto_value`] for a number of types.
274
macro_rules! impl_not_auto_value_for_types {
275
    ($($ty:ty)*) => {
276
        $(impl_not_auto_value!($ty);)*
277
    }
278
}
279

            
280
// Implement `NotAutoValue` for various primitive types.
281
impl_not_auto_value_for_types!(
282
    i8 i16 i32 i64 i128 isize
283
    u8 u16 u32 u64 u128 usize
284
    f32 f64
285
    char
286
    bool
287
);
288

            
289
use tor_basic_utils::ByteQty;
290
impl_not_auto_value!(ByteQty);
291

            
292
// TODO implement `NotAutoValue` for other types too
293

            
294
/// Padding enablement - rough amount of padding requested
295
///
296
/// Padding is cover traffic, used to help mitigate traffic analysis,
297
/// obscure traffic patterns, and impede router-level data collection.
298
///
299
/// This same enum is used to control padding at various levels of the Tor system.
300
//
301
// (TODO circpad: actually we don't negotiate circuit padding yet. But when we do, we should look at
302
// this.)
303
//
304
// This slightly-odd interleaving of derives and attributes stops rustfmt doing a daft thing
305
#[derive(Clone, Copy, Hash, Debug, Ord, PartialOrd, Eq, PartialEq)]
306
#[allow(clippy::exhaustive_enums)] // we will add variants very rarely if ever
307
#[derive(Serialize, Deserialize)]
308
#[serde(try_from = "PaddingLevelSerde", into = "PaddingLevelSerde")]
309
#[derive(Display, EnumString, IntoStaticStr)]
310
#[strum(serialize_all = "snake_case")]
311
#[derive(Default)]
312
pub enum PaddingLevel {
313
    /// Disable padding completely
314
    None,
315
    /// Reduced padding (eg for mobile)
316
    Reduced,
317
    /// Normal padding (the default)
318
    #[default]
319
    Normal,
320
}
321

            
322
/// How we (de) serialize a [`PaddingLevel`]
323
#[derive(Serialize, Deserialize)]
324
#[serde(untagged)]
325
enum PaddingLevelSerde {
326
    /// String (in snake case)
327
    ///
328
    /// We always serialize this way
329
    String(Cow<'static, str>),
330
    /// bool
331
    Bool(bool),
332
}
333

            
334
impl From<PaddingLevel> for PaddingLevelSerde {
335
8
    fn from(pl: PaddingLevel) -> PaddingLevelSerde {
336
8
        PaddingLevelSerde::String(<&str>::from(&pl).into())
337
8
    }
338
}
339

            
340
/// Padding level configuration is invalid
341
#[derive(thiserror::Error, Debug, Clone)]
342
#[non_exhaustive]
343
#[error("Invalid padding level")]
344
struct InvalidPaddingLevel {}
345

            
346
impl TryFrom<PaddingLevelSerde> for PaddingLevel {
347
    type Error = InvalidPaddingLevel;
348

            
349
132
    fn try_from(pls: PaddingLevelSerde) -> Result<PaddingLevel, Self::Error> {
350
132
        Ok(match pls {
351
128
            PaddingLevelSerde::String(s) => {
352
130
                s.as_ref().try_into().map_err(|_| InvalidPaddingLevel {})?
353
            }
354
2
            PaddingLevelSerde::Bool(false) => PaddingLevel::None,
355
2
            PaddingLevelSerde::Bool(true) => PaddingLevel::Normal,
356
        })
357
132
    }
358
}
359

            
360
#[cfg(test)]
361
mod test {
362
    // @@ begin test lint list maintained by maint/add_warning @@
363
    #![allow(clippy::bool_assert_comparison)]
364
    #![allow(clippy::clone_on_copy)]
365
    #![allow(clippy::dbg_macro)]
366
    #![allow(clippy::mixed_attributes_style)]
367
    #![allow(clippy::print_stderr)]
368
    #![allow(clippy::print_stdout)]
369
    #![allow(clippy::single_char_pattern)]
370
    #![allow(clippy::unwrap_used)]
371
    #![allow(clippy::unchecked_time_subtraction)]
372
    #![allow(clippy::useless_vec)]
373
    #![allow(clippy::needless_pass_by_value)]
374
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
375
    use super::*;
376

            
377
    #[derive(Debug, Default, Deserialize, Serialize)]
378
    struct TestConfigFile {
379
        #[serde(default)]
380
        something_enabled: BoolOrAuto,
381

            
382
        #[serde(default)]
383
        padding: PaddingLevel,
384

            
385
        #[serde(default)]
386
        auto_or_usize: ExplicitOrAuto<usize>,
387

            
388
        #[serde(default)]
389
        auto_or_bool: ExplicitOrAuto<bool>,
390
    }
391

            
392
    #[test]
393
    fn bool_or_auto() {
394
        use BoolOrAuto as BoA;
395

            
396
        let chk = |pl, s| {
397
            let tc: TestConfigFile = toml::from_str(s).expect(s);
398
            assert_eq!(pl, tc.something_enabled, "{:?}", s);
399
        };
400

            
401
        chk(BoA::Auto, "");
402
        chk(BoA::Auto, r#"something_enabled = "auto""#);
403
        chk(BoA::Explicit(true), r#"something_enabled = true"#);
404
        chk(BoA::Explicit(true), r#"something_enabled = "true""#);
405
        chk(BoA::Explicit(false), r#"something_enabled = false"#);
406
        chk(BoA::Explicit(false), r#"something_enabled = "false""#);
407

            
408
        let chk_e = |s| {
409
            let tc: Result<TestConfigFile, _> = toml::from_str(s);
410
            let _ = tc.expect_err(s);
411
        };
412

            
413
        chk_e(r#"something_enabled = 1"#);
414
        chk_e(r#"something_enabled = "unknown""#);
415
        chk_e(r#"something_enabled = "True""#);
416
    }
417

            
418
    #[test]
419
    fn padding_level() {
420
        use PaddingLevel as PL;
421

            
422
        let chk = |pl, s| {
423
            let tc: TestConfigFile = toml::from_str(s).expect(s);
424
            assert_eq!(pl, tc.padding, "{:?}", s);
425
        };
426

            
427
        chk(PL::None, r#"padding = "none""#);
428
        chk(PL::None, r#"padding = false"#);
429
        chk(PL::Reduced, r#"padding = "reduced""#);
430
        chk(PL::Normal, r#"padding = "normal""#);
431
        chk(PL::Normal, r#"padding = true"#);
432
        chk(PL::Normal, "");
433

            
434
        let chk_e = |s| {
435
            let tc: Result<TestConfigFile, _> = toml::from_str(s);
436
            let _ = tc.expect_err(s);
437
        };
438

            
439
        chk_e(r#"padding = 1"#);
440
        chk_e(r#"padding = "unknown""#);
441
        chk_e(r#"padding = "Normal""#);
442
    }
443

            
444
    #[test]
445
    fn explicit_or_auto() {
446
        use ExplicitOrAuto as EOA;
447

            
448
        let chk = |eoa: EOA<usize>, s| {
449
            let tc: TestConfigFile = toml::from_str(s).expect(s);
450
            assert_eq!(
451
                format!("{:?}", eoa),
452
                format!("{:?}", tc.auto_or_usize),
453
                "{:?}",
454
                s
455
            );
456
        };
457

            
458
        chk(EOA::Auto, r#"auto_or_usize = "auto""#);
459
        chk(EOA::Explicit(20), r#"auto_or_usize = 20"#);
460

            
461
        let chk_e = |s| {
462
            let tc: Result<TestConfigFile, _> = toml::from_str(s);
463
            let _ = tc.expect_err(s);
464
        };
465

            
466
        chk_e(r#"auto_or_usize = """#);
467
        chk_e(r#"auto_or_usize = []"#);
468
        chk_e(r#"auto_or_usize = {}"#);
469

            
470
        let chk = |eoa: EOA<bool>, s| {
471
            let tc: TestConfigFile = toml::from_str(s).expect(s);
472
            assert_eq!(
473
                format!("{:?}", eoa),
474
                format!("{:?}", tc.auto_or_bool),
475
                "{:?}",
476
                s
477
            );
478
        };
479

            
480
        // ExplicitOrAuto<bool> works just like BoolOrAuto
481
        chk(EOA::Auto, r#"auto_or_bool = "auto""#);
482
        chk(EOA::Explicit(false), r#"auto_or_bool = false"#);
483

            
484
        chk_e(r#"auto_or_bool= "not bool or auto""#);
485

            
486
        let mut config = TestConfigFile::default();
487
        let toml = toml::to_string(&config).unwrap();
488
        assert_eq!(
489
            toml,
490
            r#"something_enabled = "auto"
491
padding = "normal"
492
auto_or_usize = "auto"
493
auto_or_bool = "auto"
494
"#
495
        );
496

            
497
        config.auto_or_bool = ExplicitOrAuto::Explicit(true);
498
        let toml = toml::to_string(&config).unwrap();
499
        assert_eq!(
500
            toml,
501
            r#"something_enabled = "auto"
502
padding = "normal"
503
auto_or_usize = "auto"
504
auto_or_bool = true
505
"#
506
        );
507
    }
508
}