kanidmd_lib/
utils.rs

1//! `utils.rs` - the projects kitchen junk drawer.
2
3use crate::prelude::*;
4use hashbrown::HashSet;
5use rand::distr::{Distribution, Uniform};
6use rand::{rng, Rng};
7use std::ops::Range;
8
9#[derive(Debug)]
10pub struct DistinctAlpha;
11
12pub type Sid = [u8; 4];
13
14pub fn uuid_to_gid_u32(u: Uuid) -> u32 {
15    let b_ref = u.as_bytes();
16    let mut x: [u8; 4] = [0; 4];
17    x.clone_from_slice(&b_ref[12..16]);
18    u32::from_be_bytes(x)
19}
20
21fn uuid_from_u64_u32(a: u64, b: u32, sid: Sid) -> Uuid {
22    let mut v: Vec<u8> = Vec::with_capacity(16);
23    v.extend_from_slice(&a.to_be_bytes());
24    v.extend_from_slice(&b.to_be_bytes());
25    v.extend_from_slice(&sid);
26
27    #[allow(clippy::expect_used)]
28    uuid::Builder::from_slice(v.as_slice())
29        .expect("invalid slice for uuid builder")
30        .into_uuid()
31}
32
33pub fn uuid_from_duration(d: Duration, sid: Sid) -> Uuid {
34    uuid_from_u64_u32(d.as_secs(), d.subsec_nanos(), sid)
35}
36
37pub(crate) fn password_from_random_len(len: u32) -> String {
38    rng()
39        .sample_iter(&DistinctAlpha)
40        .take(len as usize)
41        .collect::<String>()
42}
43
44pub fn password_from_random() -> String {
45    password_from_random_len(48)
46}
47
48pub fn backup_code_from_random() -> HashSet<String> {
49    (0..8).map(|_| readable_password_from_random()).collect()
50}
51
52pub fn readable_password_from_random() -> String {
53    // 2^112 bits, means we need at least 55^20 to have as many bits of entropy.
54    // this leads us to 4 groups of 5 to create 55^20
55    let mut trng = rng();
56    format!(
57        "{}-{}-{}-{}",
58        (&mut trng)
59            .sample_iter(&DistinctAlpha)
60            .take(5)
61            .collect::<String>(),
62        (&mut trng)
63            .sample_iter(&DistinctAlpha)
64            .take(5)
65            .collect::<String>(),
66        (&mut trng)
67            .sample_iter(&DistinctAlpha)
68            .take(5)
69            .collect::<String>(),
70        (&mut trng)
71            .sample_iter(&DistinctAlpha)
72            .take(5)
73            .collect::<String>(),
74    )
75}
76
77impl Distribution<char> for DistinctAlpha {
78    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> char {
79        const RANGE: u32 = 55;
80        const GEN_ASCII_STR_CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ\
81                abcdefghjkpqrstuvwxyz\
82                0123456789";
83        // TODO: this needs to handle the error, maybe?
84        #[allow(clippy::expect_used)]
85        let range = Uniform::new(0, RANGE).expect("Failed to get a uniform range");
86
87        let n = range.sample(rng);
88        GEN_ASCII_STR_CHARSET[n as usize] as char
89    }
90}
91
92pub(crate) struct GraphemeClusterIter<'a> {
93    value: &'a str,
94    char_bounds: Vec<usize>,
95    window: usize,
96    range: Range<usize>,
97}
98
99impl<'a> GraphemeClusterIter<'a> {
100    pub fn new(value: &'a str, window: usize) -> Self {
101        let char_bounds = if value.len() < window {
102            Vec::with_capacity(0)
103        } else {
104            let mut char_bounds = Vec::with_capacity(value.len());
105            for idx in 0..value.len() {
106                if value.is_char_boundary(idx) {
107                    char_bounds.push(idx);
108                }
109            }
110            char_bounds.push(value.len());
111            char_bounds
112        };
113
114        let window_max = char_bounds.len().saturating_sub(window);
115        let range = 0..window_max;
116
117        GraphemeClusterIter {
118            value,
119            char_bounds,
120            window,
121            range,
122        }
123    }
124}
125
126impl<'a> Iterator for GraphemeClusterIter<'a> {
127    type Item = &'a str;
128
129    fn next(&mut self) -> Option<&'a str> {
130        self.range.next().map(|idx| {
131            let min = self.char_bounds[idx];
132            let max = self.char_bounds[idx + self.window];
133            &self.value[min..max]
134        })
135    }
136
137    fn size_hint(&self) -> (usize, Option<usize>) {
138        let clusters = self.char_bounds.len().saturating_sub(1);
139        (clusters, Some(clusters))
140    }
141}
142
143pub(crate) fn trigraph_iter(value: &str) -> impl Iterator<Item = &str> {
144    GraphemeClusterIter::new(value, 3)
145        .chain(GraphemeClusterIter::new(value, 2))
146        .chain(GraphemeClusterIter::new(value, 1))
147}
148
149#[cfg(test)]
150mod tests {
151    use crate::prelude::*;
152    use std::time::Duration;
153
154    use crate::utils::{uuid_from_duration, uuid_to_gid_u32, GraphemeClusterIter};
155
156    #[test]
157    fn test_utils_uuid_from_duration() {
158        let u1 = uuid_from_duration(Duration::from_secs(1), [0xff; 4]);
159        assert_eq!(
160            "00000000-0000-0001-0000-0000ffffffff",
161            u1.as_hyphenated().to_string()
162        );
163
164        let u2 = uuid_from_duration(Duration::from_secs(1000), [0xff; 4]);
165        assert_eq!(
166            "00000000-0000-03e8-0000-0000ffffffff",
167            u2.as_hyphenated().to_string()
168        );
169    }
170
171    #[test]
172    fn test_utils_uuid_to_gid_u32() {
173        let u1 = uuid!("00000000-0000-0001-0000-000000000000");
174        let r1 = uuid_to_gid_u32(u1);
175        assert_eq!(r1, 0);
176
177        let u2 = uuid!("00000000-0000-0001-0000-0000ffffffff");
178        let r2 = uuid_to_gid_u32(u2);
179        assert_eq!(r2, 0xffffffff);
180
181        let u3 = uuid!("00000000-0000-0001-0000-ffff12345678");
182        let r3 = uuid_to_gid_u32(u3);
183        assert_eq!(r3, 0x12345678);
184    }
185
186    #[test]
187    fn test_utils_grapheme_cluster_iter() {
188        let d = "❤️🧡💛💚💙💜";
189
190        let gc_expect = vec!["❤", "\u{fe0f}", "🧡", "💛", "💚", "💙", "💜"];
191        let gc: Vec<_> = GraphemeClusterIter::new(d, 1).collect();
192        assert_eq!(gc, gc_expect);
193
194        let gc_expect = vec!["❤\u{fe0f}", "\u{fe0f}🧡", "🧡💛", "💛💚", "💚💙", "💙💜"];
195        let gc: Vec<_> = GraphemeClusterIter::new(d, 2).collect();
196        assert_eq!(gc, gc_expect);
197
198        let gc_expect = vec!["❤\u{fe0f}🧡", "\u{fe0f}🧡💛", "🧡💛💚", "💛💚💙", "💚💙💜"];
199        let gc: Vec<_> = GraphemeClusterIter::new(d, 3).collect();
200        assert_eq!(gc, gc_expect);
201
202        let d = "🤷🏿‍♂️";
203
204        let gc_expect = vec!["🤷", "🏿", "\u{200d}", "♂", "\u{fe0f}"];
205        let gc: Vec<_> = GraphemeClusterIter::new(d, 1).collect();
206        assert_eq!(gc, gc_expect);
207
208        let gc_expect = vec!["🤷🏿", "🏿\u{200d}", "\u{200d}♂", "♂\u{fe0f}"];
209        let gc: Vec<_> = GraphemeClusterIter::new(d, 2).collect();
210        assert_eq!(gc, gc_expect);
211
212        let gc_expect = vec!["🤷🏿\u{200d}", "🏿\u{200d}♂", "\u{200d}♂\u{fe0f}"];
213        let gc: Vec<_> = GraphemeClusterIter::new(d, 3).collect();
214        assert_eq!(gc, gc_expect);
215    }
216}