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

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

            
63
use std::ops::Deref;
64

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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