1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![allow(clippy::cognitive_complexity)] // See arti#2556
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_time_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
#![allow(clippy::collapsible_if)] // See arti#2342
46
#![deny(clippy::unused_async)]
47
#![deny(clippy::string_slice)] // See arti#2571
48
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
49
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
50

            
51
// TODO: Try making it not Deref and having expose+expose_mut instead; how bad is it?
52

            
53
use educe::Educe;
54
#[cfg(feature = "serde")]
55
use serde::{Deserialize, Serialize};
56

            
57
mod err;
58
mod flags;
59
mod impls;
60
pub mod util;
61

            
62
pub use err::Error;
63
pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
64

            
65
use std::ops::Deref;
66

            
67
/// A `Result` returned by the flag-manipulation functions in `safelog`.
68
pub type Result<T> = std::result::Result<T, Error>;
69

            
70
// Re-exported for macros.
71
#[doc(hidden)]
72
pub use flags::unsafe_logging_enabled;
73

            
74
/// A wrapper type for a sensitive value.
75
///
76
/// By default, a `Sensitive<T>` behaves the same as a regular `T`, except that
77
/// attempts to turn it into a string (via `Display`, `Debug`, etc) all produce
78
/// the string `[scrubbed]`.
79
///
80
/// This behavior can be overridden locally by using
81
/// [`with_safe_logging_suppressed`] and globally with [`disable_safe_logging`].
82
#[derive(Educe, Clone, Copy)]
83
#[educe(
84
    Default(bound),
85
    Deref,
86
    DerefMut,
87
    Eq(bound),
88
    Hash(bound),
89
    Ord(bound),
90
    PartialEq(bound),
91
    PartialOrd(bound)
92
)]
93
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94
#[cfg_attr(feature = "serde", serde(transparent))]
95
pub struct Sensitive<T>(T);
96

            
97
impl<T> Sensitive<T> {
98
    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
99
71694
    pub fn new(value: T) -> Self {
100
71694
        Sensitive(value)
101
71694
    }
102

            
103
    /// Extract the inner value from this `Sensitive<T>`.
104
1530
    pub fn into_inner(self) -> T {
105
1530
        self.0
106
1530
    }
107

            
108
    /// Extract the inner value from this `Sensitive<T>`.
109
    #[deprecated = "Use the new into_inner method instead"]
110
2
    pub fn unwrap(sensitive: Sensitive<T>) -> T {
111
2
        sensitive.into_inner()
112
2
    }
113

            
114
    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
115
2
    pub fn as_ref(&self) -> Sensitive<&T> {
116
2
        Sensitive(&self.0)
117
2
    }
118

            
119
    /// Return a reference to the inner value
120
    //
121
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-sensitivisation"
122
    // via what is usually a semantically-neutral interface.
123
173
    pub fn as_inner(&self) -> &T {
124
173
        &self.0
125
173
    }
126
}
127

            
128
/// Wrap a value as `Sensitive`.
129
///
130
/// This function is an alias for [`Sensitive::new`].
131
28
pub fn sensitive<T>(value: T) -> Sensitive<T> {
132
28
    Sensitive(value)
133
28
}
134

            
135
impl<T> From<T> for Sensitive<T> {
136
71678
    fn from(value: T) -> Self {
137
71678
        Sensitive::new(value)
138
71678
    }
139
}
140

            
141
/// Helper: Declare one or more Display-like implementations for a
142
/// Sensitive-like type.  These implementations will delegate to their std::fmt
143
/// types if safe logging is disabled, and write `[scrubbed]` otherwise.
144
macro_rules! impl_display_traits {
145
    { $($trait:ident),* } => {
146
    $(
147
        impl<T: std::fmt::$trait> std::fmt::$trait for Sensitive<T> {
148
92
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149
92
                if flags::unsafe_logging_enabled() {
150
32
                    std::fmt::$trait::fmt(&self.0, f)
151
                } else {
152
60
                    write!(f, "[scrubbed]")
153
                }
154
92
            }
155
        }
156

            
157
        impl<T: std::fmt::$trait> std::fmt::$trait for BoxSensitive<T> {
158
            #[inline]
159
8
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160
8
                std::fmt::$trait::fmt(&*self.0, f)
161
8
            }
162
        }
163
   )*
164
   }
165
}
166

            
167
/// A wrapper suitable for logging and including in errors
168
///
169
/// This is a newtype around `Box<Sensitive<T>>`.
170
///
171
/// This is useful particularly in errors,
172
/// where the box can help reduce the size of error variants
173
/// (for example ones containing large values like an `OwnedChanTarget`).
174
///
175
/// `BoxSensitive<T>` dereferences to [`Sensitive<T>`].
176
//
177
// Making it be a newtype rather than a type alias allows us to implement
178
// `into_inner` and `From<T>` and so on.
179
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
180
pub struct BoxSensitive<T>(Box<Sensitive<T>>);
181

            
182
impl<T> From<T> for BoxSensitive<T> {
183
2
    fn from(t: T) -> BoxSensitive<T> {
184
2
        BoxSensitive(Box::new(sensitive(t)))
185
2
    }
186
}
187

            
188
impl<T> BoxSensitive<T> {
189
    /// Return the innermost `T`
190
2
    pub fn into_inner(self) -> T {
191
        // TODO want unstable Box::into_inner(self.0) rust-lang/rust/issues/80437
192
2
        let unboxed = *self.0;
193
2
        unboxed.into_inner()
194
2
    }
195
}
196

            
197
impl<T> Deref for BoxSensitive<T> {
198
    type Target = Sensitive<T>;
199

            
200
2
    fn deref(&self) -> &Sensitive<T> {
201
2
        &self.0
202
2
    }
203
}
204

            
205
impl_display_traits! {
206
    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
207
}
208

            
209
/// An object that may or may not be sensitive.
210
///
211
/// See [`Sensitive`] for the guarantees it provides for the sensitive case.
212
#[derive(Clone, derive_more::Display)]
213
pub struct MaybeSensitive<T>(either::Either<T, Sensitive<T>>);
214

            
215
impl<T> MaybeSensitive<T> {
216
    /// Build a sensitive container.
217
4
    pub fn sensitive(t: T) -> Self {
218
4
        Self(either::Either::Right(Sensitive::new(t)))
219
4
    }
220

            
221
    /// Build a non sensitive container.
222
634
    pub fn not_sensitive(t: T) -> Self {
223
634
        Self(either::Either::Left(t))
224
634
    }
225

            
226
    /// Return the innermost `T`
227
2
    pub fn inner(self) -> T {
228
2
        match self.0 {
229
            either::Either::Left(t) => t,
230
2
            either::Either::Right(s) => s.into_inner(),
231
        }
232
2
    }
233

            
234
    /// Map a `MaybeSensitive<T>` to a `MaybeSensitive<U>`
235
    /// by applying the supplied function `f` to the inner `T`
236
568
    pub fn map<U, F>(self, f: F) -> MaybeSensitive<U>
237
568
    where
238
568
        F: FnOnce(T) -> U,
239
    {
240
568
        match self.0 {
241
566
            either::Either::Left(t) => MaybeSensitive(either::Either::Left(f(t))),
242
2
            either::Either::Right(s) => {
243
2
                let new_inner = f(s.into_inner());
244
2
                MaybeSensitive(either::Either::Right(Sensitive::new(new_inner)))
245
            }
246
        }
247
568
    }
248
}
249

            
250
impl<T: std::fmt::Debug> std::fmt::Debug for MaybeSensitive<T> {
251
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252
        use std::fmt::Debug;
253
        match &self.0 {
254
            either::Either::Left(v) => Debug::fmt(v, f),
255
            either::Either::Right(v) => Debug::fmt(v, f),
256
        }
257
    }
258
}
259

            
260
impl<T> Deref for MaybeSensitive<T> {
261
    type Target = T;
262

            
263
225
    fn deref(&self) -> &T {
264
225
        match &self.0 {
265
70
            either::Either::Left(t) => t,
266
155
            either::Either::Right(s) => s.as_inner(),
267
        }
268
225
    }
269
}
270

            
271
/// A `redactable` object is one where we know a way to display _part_ of it
272
/// when we are running with safe logging enabled.
273
///
274
/// For example, instead of referring to a user as `So-and-So` or `[scrubbed]`,
275
/// this trait would allow referring to the user as `S[...]`.
276
///
277
/// # Privacy notes
278
///
279
/// Displaying some information about an object is always less safe than
280
/// displaying no information about it!
281
///
282
/// For example, in an environment with only a small number of users, the first
283
/// letter of a user's name might be plenty of information to identify them
284
/// uniquely.
285
///
286
/// Even if a piece of redacted information is safe on its own, several pieces
287
/// of redacted information, when taken together, can be enough for an adversary
288
/// to infer more than you want.  For example, if you log somebody's first
289
/// initial, month of birth, and last-two-digits of ID number, you have just
290
/// discarded 99.9% of potential individuals from the attacker's consideration.
291
pub trait Redactable: std::fmt::Display + std::fmt::Debug {
292
    /// As `Display::fmt`, but produce a redacted representation.
293
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
294
    /// As `Debug::fmt`, but produce a redacted representation.
295
4
    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296
4
        self.display_redacted(f)
297
4
    }
298
    /// Return a smart pointer that will display or debug this object as its
299
    /// redacted form.
300
38
    fn redacted(&self) -> Redacted<&Self> {
301
38
        Redacted(self)
302
38
    }
303
    /// Return a smart pointer that redacts this object if `redact` is true.
304
60994
    fn maybe_redacted(&self, redact: bool) -> MaybeRedacted<&Self> {
305
60994
        if redact {
306
6
            MaybeRedacted(either::Either::Right(Redacted(self)))
307
        } else {
308
60988
            MaybeRedacted(either::Either::Left(self))
309
        }
310
60994
    }
311
}
312

            
313
impl<'a, T: Redactable + ?Sized> Redactable for &'a T {
314
126
    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315
126
        (*self).display_redacted(f)
316
126
    }
317
}
318

            
319
/// A wrapper around a `Redactable` that displays it in redacted format.
320
#[derive(Educe, Clone, Copy)]
321
#[educe(
322
    Default(bound),
323
    Deref,
324
    DerefMut,
325
    Eq(bound),
326
    Hash(bound),
327
    Ord(bound),
328
    PartialEq(bound),
329
    PartialOrd(bound)
330
)]
331
#[derive(derive_more::From)]
332
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
333
#[cfg_attr(feature = "serde", serde(transparent))]
334
pub struct Redacted<T: Redactable>(T);
335

            
336
impl<T: Redactable> Redacted<T> {
337
    /// Create a new `Redacted`.
338
2
    pub fn new(value: T) -> Self {
339
2
        Self(value)
340
2
    }
341

            
342
    /// Consume this wrapper and return its inner value.
343
2
    pub fn unwrap(self) -> T {
344
2
        self.0
345
2
    }
346

            
347
    /// Converts `&Redacted<T>` to `Redacted<&T>`
348
    pub fn as_ref(&self) -> Redacted<&T> {
349
        Redacted(&self.0)
350
    }
351

            
352
    /// Return a reference to the inner value
353
    //
354
    // This isn't `AsRef` or `as_ref` because we don't want to offer "de-redaction"
355
    // via what is usually a semantically-neutral interface.
356
870
    pub fn as_inner(&self) -> &T {
357
870
        &self.0
358
870
    }
359
}
360

            
361
impl<T: Redactable> std::fmt::Display for Redacted<T> {
362
38
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363
38
        if flags::unsafe_logging_enabled() {
364
2
            std::fmt::Display::fmt(&self.0, f)
365
        } else {
366
36
            self.0.display_redacted(f)
367
        }
368
38
    }
369
}
370

            
371
impl<T: Redactable> std::fmt::Debug for Redacted<T> {
372
6
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373
6
        if flags::unsafe_logging_enabled() {
374
2
            std::fmt::Debug::fmt(&self.0, f)
375
        } else {
376
4
            self.0.debug_redacted(f)
377
        }
378
6
    }
379
}
380

            
381
/// An object that may or may not be redacted.
382
///
383
/// Used to implement conditional redaction
384
#[derive(Clone, derive_more::Display)]
385
pub struct MaybeRedacted<T: Redactable>(either::Either<T, Redacted<T>>);
386

            
387
impl<T: Redactable> std::fmt::Debug for MaybeRedacted<T> {
388
4
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389
        use std::fmt::Debug;
390
4
        match &self.0 {
391
2
            either::Either::Left(v) => Debug::fmt(v, f),
392
2
            either::Either::Right(v) => Debug::fmt(v, f),
393
        }
394
4
    }
395
}
396

            
397
/// A type that can be displayed in a redacted or un-redacted form,
398
/// but which forces the caller to choose.
399
///
400
/// See [`Redactable`] for more discussion on redaction.
401
///
402
/// Unlike [`Redactable`], this type is "inherently sensitive":
403
/// Types implementing `DisplayRedacted` should not typically implement
404
/// [`Display`](std::fmt::Display).
405
///
406
/// For external types that implement `Display`,
407
/// or for types which are usually _not_ sensitive,
408
/// `Redacted` is likely a better choice.
409
pub trait DisplayRedacted {
410
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
411
    /// in its redacted form.
412
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
413
    /// As [`Display::fmt`](std::fmt::Display::fmt), but write this object
414
    /// in its un-redacted form.
415
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
416

            
417
    // TODO: At some point in the future, when default values are supported for GATs,
418
    // it might be good to turn these RPIT functions into associated types.
419

            
420
    /// Return a pointer wrapping this object that can be Displayed in redacted form
421
    /// if safe-logging is enabled.
422
    ///
423
    /// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
424
2152
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
425
2152
        DispRedacted(self)
426
2152
    }
427
    /// Return a pointer wrapping this object that can be Displayed in unredacted form.
428
54950
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
429
54950
        DispUnredacted(self)
430
54950
    }
431
}
432

            
433
impl<'a, T> DisplayRedacted for &'a T
434
where
435
    T: DisplayRedacted + ?Sized,
436
{
437
    fn display_redacted(&self) -> impl std::fmt::Display + '_ {
438
        (*self).display_redacted()
439
    }
440
    fn display_unredacted(&self) -> impl std::fmt::Display + '_ {
441
        (*self).display_unredacted()
442
    }
443
2152
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444
2152
        (*self).fmt_redacted(f)
445
2152
    }
446
54954
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447
54954
        (*self).fmt_unredacted(f)
448
54954
    }
449
}
450

            
451
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
452
/// by displaying the object in its redacted form
453
/// if safe-logging is enabled.
454
///
455
/// (If safe-logging is not enabled, it will de displayed in its unredacted form.)
456
#[allow(clippy::exhaustive_structs)]
457
#[derive(derive_more::AsRef)]
458
pub struct DispRedacted<T: ?Sized>(pub T);
459

            
460
/// A wrapper around a [`DisplayRedacted`] that implements [`Display`](std::fmt::Display)
461
/// by displaying the object in its un-redacted form.
462
#[allow(clippy::exhaustive_structs)]
463
#[derive(derive_more::AsRef)]
464
pub struct DispUnredacted<T: ?Sized>(pub T);
465

            
466
impl<T> std::fmt::Display for DispRedacted<T>
467
where
468
    T: DisplayRedacted + ?Sized,
469
{
470
3602
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471
3602
        if crate::flags::unsafe_logging_enabled() {
472
2
            self.0.fmt_unredacted(f)
473
        } else {
474
3600
            self.0.fmt_redacted(f)
475
        }
476
3602
    }
477
}
478

            
479
impl<T> std::fmt::Display for DispUnredacted<T>
480
where
481
    T: DisplayRedacted + ?Sized,
482
{
483
54952
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484
54952
        self.0.fmt_unredacted(f)
485
54952
    }
486
}
487

            
488
/// A type that can be debugged in a redacted or un-redacted form,
489
/// but which forces the caller to choose.
490
///
491
/// See [`Redactable`] for more discussion on redaction.
492
///
493
/// Unlike [`Redactable`], this type is "inherently sensitive":
494
/// [`Debug`](std::fmt::Debug) will display it in redacted or un-redacted format
495
/// depending on whether safe logging is enabled.
496
///
497
/// For external types that implement `Debug`,
498
/// or for types which are usually _not_ sensitive,
499
/// `Redacted` is likely a better choice.
500
pub trait DebugRedacted {
501
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
502
    /// in its redacted form.
503
    fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
504
    /// As [`Debug::fmt`](std::fmt::Debug::fmt), but write this object
505
    /// in its unredacted form.
506
    fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
507
}
508

            
509
/// Implement [`std::fmt::Debug`] for a type that implements [`DebugRedacted`].
510
///
511
/// The implementation will use fmt_redacted() when safe-logging is enabled,
512
/// and fmt_unredacted() otherwise.
513
///
514
/// (NOTE we can't just write 'impl<T:DebugRedacted> Debug for T`;
515
/// Rust doesn't like it.)
516
#[macro_export]
517
macro_rules! derive_redacted_debug {
518
    {$t:ty} => {
519
    impl std::fmt::Debug for $t {
520
1748
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521
1748
            if $crate::unsafe_logging_enabled() {
522
4
                $crate::DebugRedacted::fmt_unredacted(self, f)
523
            } else {
524
1744
                $crate::DebugRedacted::fmt_redacted(self, f)
525
            }
526
1748
        }
527
    }
528
}}
529

            
530
#[cfg(test)]
531
mod test {
532
    // @@ begin test lint list maintained by maint/add_warning @@
533
    #![allow(clippy::bool_assert_comparison)]
534
    #![allow(clippy::clone_on_copy)]
535
    #![allow(clippy::dbg_macro)]
536
    #![allow(clippy::mixed_attributes_style)]
537
    #![allow(clippy::print_stderr)]
538
    #![allow(clippy::print_stdout)]
539
    #![allow(clippy::single_char_pattern)]
540
    #![allow(clippy::unwrap_used)]
541
    #![allow(clippy::unchecked_time_subtraction)]
542
    #![allow(clippy::useless_vec)]
543
    #![allow(clippy::needless_pass_by_value)]
544
    #![allow(clippy::string_slice)] // See arti#2571
545
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
546

            
547
    use super::*;
548
    use serial_test::serial;
549
    use static_assertions::{assert_impl_all, assert_not_impl_any};
550

            
551
    #[test]
552
    fn clone_bound() {
553
        // Here we'll make sure that educe bounds work about the way we expect.
554
        #[derive(Clone)]
555
        struct A;
556
        struct B;
557

            
558
        let _x = Sensitive(A).clone();
559
        let _y = Sensitive(B);
560

            
561
        assert_impl_all!(Sensitive<A> : Clone);
562
        assert_not_impl_any!(Sensitive<B> : Clone);
563
    }
564

            
565
    #[test]
566
    #[serial]
567
    fn debug_vec() {
568
        type SVec = Sensitive<Vec<u32>>;
569

            
570
        let mut sv = SVec::default();
571
        assert!(sv.is_empty());
572
        sv.push(104);
573
        sv.push(49);
574
        assert_eq!(sv.len(), 2);
575

            
576
        assert!(!flags::unsafe_logging_enabled());
577
        assert_eq!(format!("{:?}", sv), "[scrubbed]");
578
        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
579
        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
580
        let normal = with_safe_logging_suppressed(|| format!("{:?}", sv));
581
        assert_eq!(normal, "[104, 49]");
582

            
583
        let _g = disable_safe_logging().unwrap();
584
        assert_eq!(format!("{:?}", sv), "[104, 49]");
585

            
586
        assert_eq!(sv, SVec::from(vec![104, 49]));
587
        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
588
        assert_eq!(*sv, vec![104, 49]);
589
    }
590

            
591
    #[test]
592
    #[serial]
593
    #[allow(deprecated)]
594
    fn deprecated() {
595
        type SVec = Sensitive<Vec<u32>>;
596
        let sv = Sensitive(vec![104, 49]);
597

            
598
        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
599
    }
600

            
601
    #[test]
602
    #[serial]
603
    fn display_various() {
604
        let val = Sensitive::<u32>::new(0x0ed19a);
605

            
606
        let closure1 = || {
607
            format!(
608
                "{:?}, {}, {:o}, {:x}, {:X}, {:b}",
609
                val, val, val, val, val, val,
610
            )
611
        };
612
        let s1 = closure1();
613
        let s2 = with_safe_logging_suppressed(closure1);
614
        assert_eq!(
615
            s1,
616
            "[scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed], [scrubbed]"
617
        );
618
        assert_eq!(
619
            s2,
620
            "971162, 971162, 3550632, ed19a, ED19A, 11101101000110011010"
621
        );
622

            
623
        let n = 1.0E32;
624
        let val = Sensitive::<f64>::new(n);
625
        let expect = format!("{:?}, {}, {:e}, {:E}", n, n, n, n);
626
        let closure2 = || format!("{:?}, {}, {:e}, {:E}", val, val, val, val);
627
        let s1 = closure2();
628
        let s2 = with_safe_logging_suppressed(closure2);
629
        assert_eq!(s1, "[scrubbed], [scrubbed], [scrubbed], [scrubbed]");
630
        assert_eq!(s2, expect);
631

            
632
        let ptr: *const u8 = std::ptr::null();
633
        let val = Sensitive::new(ptr);
634
        let expect = format!("{:?}, {:p}", ptr, ptr);
635
        let closure3 = || format!("{:?}, {:p}", val, val);
636
        let s1 = closure3();
637
        let s2 = with_safe_logging_suppressed(closure3);
638
        assert_eq!(s1, "[scrubbed], [scrubbed]");
639
        assert_eq!(s2, expect);
640
    }
641

            
642
    #[test]
643
    #[serial]
644
    fn box_sensitive() {
645
        let b: BoxSensitive<_> = "hello world".into();
646

            
647
        assert_eq!(b.clone().into_inner(), "hello world");
648

            
649
        let closure = || format!("{} {:?}", b, b);
650
        assert_eq!(closure(), "[scrubbed] [scrubbed]");
651
        assert_eq!(
652
            with_safe_logging_suppressed(closure),
653
            r#"hello world "hello world""#
654
        );
655

            
656
        assert_eq!(b.len(), 11);
657
    }
658

            
659
    #[test]
660
    #[serial]
661
    fn test_redacted() {
662
        let localhost = std::net::Ipv4Addr::LOCALHOST;
663
        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
664

            
665
        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
666
        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
667

            
668
        let closure = |b| {
669
            format!(
670
                "{} {:?}",
671
                localhost.maybe_redacted(b),
672
                localhost.maybe_redacted(b)
673
            )
674
        };
675
        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
676
        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
677

            
678
        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
679
    }
680

            
681
    struct RedactionCheck(u32);
682
    impl DisplayRedacted for RedactionCheck {
683
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684
            write!(f, "{}", self.0)
685
        }
686

            
687
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
688
            let v = self.0.to_string();
689
            write!(f, "{}xxx", v.chars().next().unwrap())
690
        }
691
    }
692
    impl DebugRedacted for RedactionCheck {
693
        fn fmt_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694
            write!(f, "Num({})", self.display_redacted())
695
        }
696

            
697
        fn fmt_unredacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698
            write!(f, "Num({})", self.display_unredacted())
699
        }
700
    }
701
    derive_redacted_debug!(RedactionCheck);
702

            
703
    #[test]
704
    #[serial]
705
    fn display_redacted() {
706
        let n = RedactionCheck(999);
707
        assert_eq!(&n.display_unredacted().to_string(), "999");
708
        assert_eq!(&n.display_redacted().to_string(), "9xxx");
709
        with_safe_logging_suppressed(|| assert_eq!(&n.display_redacted().to_string(), "999"));
710

            
711
        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
712
        assert_eq!(DispUnredacted(&n).to_string(), "999");
713

            
714
        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
715
        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
716
    }
717
}