fern_masking/
strategies.rs

1// SPDX-FileCopyrightText:  Copyright © 2022 The Fern Authors <team@fernproxy.io>
2// SPDX-License-Identifier: Apache-2.0
3
4use bytes::{BufMut, Bytes, BytesMut};
5use std::fmt::Debug;
6
7/// A trait defining an interface for data masking strategies.
8//TODO(ppiotr3k): consider moving to interfaces
9//TODO(ppiotr3k): refactor/closure to dedup logging code
10pub trait MaskingStrategy: Debug + Send + Sync {
11    fn mask(&self, data: &Bytes) -> Bytes;
12}
13
14/// A simple and fast masking strategy where whatever the provided data, the
15/// result will be a repetition of `*` characters of requested `length`.
16#[derive(Debug)]
17pub struct CaviarMask {
18    length: usize,
19}
20
21impl CaviarMask {
22    pub const fn new(length: usize) -> Self {
23        Self { length }
24    }
25}
26
27impl MaskingStrategy for CaviarMask {
28    fn mask(&self, data: &Bytes) -> Bytes {
29        log::trace!(" original value: {:?}", data);
30
31        let mut res = BytesMut::with_capacity(self.length);
32        res.put_bytes(b'*', self.length);
33
34        log::trace!("rewritten value: {:?}", res);
35        res.freeze()
36    }
37}
38
39/// A shape-preserving masking strategy where only alphanumeric characters from
40/// provided `data` will be replaced by `*` characters. By preserving shape,
41/// this strategy leaks both length and general aspect information.
42#[derive(Debug)]
43pub struct CaviarShapeMask;
44
45impl CaviarShapeMask {
46    pub const fn new() -> Self {
47        Self
48    }
49}
50
51impl MaskingStrategy for CaviarShapeMask {
52    //TODO(ppiotr3k): add support for UTF8
53    fn mask(&self, data: &Bytes) -> Bytes {
54        log::trace!(" original value: {:?}", data);
55
56        let mut res = BytesMut::with_capacity(data.len());
57        for c in data.iter() {
58            if (*c as char).is_alphanumeric() {
59                res.put_u8(b'*');
60            } else {
61                res.put_u8(*c);
62            }
63        }
64
65        log::trace!("rewritten value: {:?}", res);
66        res.freeze()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use bytes::Bytes;
74
75    #[test]
76    fn valid_caviar_empty_data() {
77        let data = Bytes::from_static(b"");
78        let expected = Bytes::from_static(b"******");
79
80        let strategy = CaviarMask::new(6);
81        let masked = strategy.mask(&data);
82        assert_eq!(expected, masked, "masked data");
83    }
84
85    #[test]
86    fn valid_caviar_single_char_data() {
87        let data = Bytes::from_static(b"P");
88        let expected = Bytes::from_static(b"******");
89
90        let strategy = CaviarMask::new(6);
91        let masked = strategy.mask(&data);
92        assert_eq!(expected, masked, "masked data");
93    }
94
95    #[test]
96    fn valid_caviar_shape_empty_data() {
97        let data = Bytes::from_static(b"");
98        let expected = Bytes::from_static(b"");
99
100        let strategy = CaviarShapeMask::new();
101        let masked = strategy.mask(&data);
102        assert_eq!(expected, masked, "masked data");
103    }
104
105    #[test]
106    fn valid_caviar_shape_single_hyphen_data() {
107        let data = Bytes::from_static(b"abcd-efgh");
108        let expected = Bytes::from_static(b"****-****");
109
110        let strategy = CaviarShapeMask::new();
111        let masked = strategy.mask(&data);
112        assert_eq!(expected, masked, "masked data");
113    }
114}