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
#![deny(clippy::string_slice)] // See arti#2571
48
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49

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

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

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

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

            
64
use std::ops::Deref;
65

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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