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
#![warn(clippy::cognitive_complexity)]
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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48

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

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

            
55
mod err;
56
mod flags;
57
mod impls;
58

            
59
pub use err::Error;
60
pub use flags::{Guard, disable_safe_logging, enforce_safe_logging, with_safe_logging_suppressed};
61

            
62
use std::ops::Deref;
63

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

            
67
// Re-exported for macros.
68
#[doc(hidden)]
69
pub use flags::unsafe_logging_enabled;
70

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

            
94
impl<T> Sensitive<T> {
95
    /// Create a new `Sensitive<T>`, wrapping a provided `value`.
96
74023
    pub fn new(value: T) -> Self {
97
74023
        Sensitive(value)
98
74023
    }
99

            
100
    /// Extract the inner value from this `Sensitive<T>`.
101
1482
    pub fn into_inner(self) -> T {
102
1482
        self.0
103
1482
    }
104

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

            
111
    /// Converts `&Sensitive<T>` to `Sensitive<&T>`
112
2
    pub fn as_ref(&self) -> Sensitive<&T> {
113
2
        Sensitive(&self.0)
114
2
    }
115

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

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

            
132
impl<T> From<T> for Sensitive<T> {
133
74007
    fn from(value: T) -> Self {
134
74007
        Sensitive::new(value)
135
74007
    }
136
}
137

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

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

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

            
179
impl<T> From<T> for BoxSensitive<T> {
180
2
    fn from(t: T) -> BoxSensitive<T> {
181
2
        BoxSensitive(Box::new(sensitive(t)))
182
2
    }
183
}
184

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

            
194
impl<T> Deref for BoxSensitive<T> {
195
    type Target = Sensitive<T>;
196

            
197
2
    fn deref(&self) -> &Sensitive<T> {
198
2
        &self.0
199
2
    }
200
}
201

            
202
impl_display_traits! {
203
    Display, Debug, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer
204
}
205

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

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

            
218
    /// Build a non sensitive container.
219
566
    pub fn not_sensitive(t: T) -> Self {
220
566
        Self(either::Either::Left(t))
221
566
    }
222

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

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

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

            
257
impl<T> Deref for MaybeSensitive<T> {
258
    type Target = T;
259

            
260
178
    fn deref(&self) -> &T {
261
178
        match &self.0 {
262
26
            either::Either::Left(t) => t,
263
152
            either::Either::Right(s) => s.as_inner(),
264
        }
265
178
    }
266
}
267

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

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

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

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

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

            
344
    /// Converts `&Redacted<T>` to `Redacted<&T>`
345
    pub fn as_ref(&self) -> Redacted<&T> {
346
        Redacted(&self.0)
347
    }
348

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
543
    use super::*;
544
    use serial_test::serial;
545
    use static_assertions::{assert_impl_all, assert_not_impl_any};
546

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

            
554
        let _x = Sensitive(A).clone();
555
        let _y = Sensitive(B);
556

            
557
        assert_impl_all!(Sensitive<A> : Clone);
558
        assert_not_impl_any!(Sensitive<B> : Clone);
559
    }
560

            
561
    #[test]
562
    #[serial]
563
    fn debug_vec() {
564
        type SVec = Sensitive<Vec<u32>>;
565

            
566
        let mut sv = SVec::default();
567
        assert!(sv.is_empty());
568
        sv.push(104);
569
        sv.push(49);
570
        assert_eq!(sv.len(), 2);
571

            
572
        assert!(!flags::unsafe_logging_enabled());
573
        assert_eq!(format!("{:?}", &sv), "[scrubbed]");
574
        assert_eq!(format!("{:?}", sv.as_ref()), "[scrubbed]");
575
        assert_eq!(format!("{:?}", sv.as_inner()), "[104, 49]");
576
        let normal = with_safe_logging_suppressed(|| format!("{:?}", &sv));
577
        assert_eq!(normal, "[104, 49]");
578

            
579
        let _g = disable_safe_logging().unwrap();
580
        assert_eq!(format!("{:?}", &sv), "[104, 49]");
581

            
582
        assert_eq!(sv, SVec::from(vec![104, 49]));
583
        assert_eq!(sv.clone().into_inner(), vec![104, 49]);
584
        assert_eq!(*sv, vec![104, 49]);
585
    }
586

            
587
    #[test]
588
    #[serial]
589
    #[allow(deprecated)]
590
    fn deprecated() {
591
        type SVec = Sensitive<Vec<u32>>;
592
        let sv = Sensitive(vec![104, 49]);
593

            
594
        assert_eq!(SVec::unwrap(sv), vec![104, 49]);
595
    }
596

            
597
    #[test]
598
    #[serial]
599
    fn display_various() {
600
        let val = Sensitive::<u32>::new(0x0ed19a);
601

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

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

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

            
638
    #[test]
639
    #[serial]
640
    fn box_sensitive() {
641
        let b: BoxSensitive<_> = "hello world".into();
642

            
643
        assert_eq!(b.clone().into_inner(), "hello world");
644

            
645
        let closure = || format!("{} {:?}", b, b);
646
        assert_eq!(closure(), "[scrubbed] [scrubbed]");
647
        assert_eq!(
648
            with_safe_logging_suppressed(closure),
649
            r#"hello world "hello world""#
650
        );
651

            
652
        assert_eq!(b.len(), 11);
653
    }
654

            
655
    #[test]
656
    #[serial]
657
    fn test_redacted() {
658
        let localhost = std::net::Ipv4Addr::LOCALHOST;
659
        let closure = || format!("{} {:?}", localhost.redacted(), localhost.redacted());
660

            
661
        assert_eq!(closure(), "127.x.x.x 127.x.x.x");
662
        assert_eq!(with_safe_logging_suppressed(closure), "127.0.0.1 127.0.0.1");
663

            
664
        let closure = |b| {
665
            format!(
666
                "{} {:?}",
667
                localhost.maybe_redacted(b),
668
                localhost.maybe_redacted(b)
669
            )
670
        };
671
        assert_eq!(closure(true), "127.x.x.x 127.x.x.x");
672
        assert_eq!(closure(false), "127.0.0.1 127.0.0.1");
673

            
674
        assert_eq!(Redacted::new(localhost).unwrap(), localhost);
675
    }
676

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

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

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

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

            
707
        assert_eq!(DispRedacted(&n).to_string(), "9xxx");
708
        assert_eq!(DispUnredacted(&n).to_string(), "999");
709

            
710
        assert_eq!(&format!("{n:?}"), "Num(9xxx)");
711
        with_safe_logging_suppressed(|| assert_eq!(&format!("{n:?}"), "Num(999)"));
712
    }
713
}