Skip to main content

sofie_storage/
key.rs

1use std::fmt;
2
3use sofie_core::{Error, Result};
4
5const RANDOM_BYTES: usize = 16;
6const LENGTH: usize = RANDOM_BYTES * 2;
7
8/// 128 random bits as lowercase hex: never a name a caller chose, so never a path that leaves its directory.
9#[derive(Clone, PartialEq, Eq, Hash)]
10pub struct StorageKey {
11    hex: String,
12}
13
14impl StorageKey {
15    pub fn generate() -> Result<Self> {
16        let mut random = [0; RANDOM_BYTES];
17        sofie_core::random::fill(&mut random)
18            .map_err(|error| Error::internal("generating a storage key", error))?;
19        Ok(Self {
20            hex: sofie_core::encoding::hex::encode(&random),
21        })
22    }
23
24    pub fn parse(text: &str) -> Result<Self> {
25        let is_key = text.len() == LENGTH
26            && text
27                .bytes()
28                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte));
29        if !is_key {
30            return Err(Error::invalid(
31                "storage_key",
32                "is not a storage key: 32 lowercase hexadecimal digits",
33            ));
34        }
35        Ok(Self {
36            hex: text.to_owned(),
37        })
38    }
39
40    #[must_use]
41    pub fn as_str(&self) -> &str {
42        &self.hex
43    }
44
45    #[cfg(feature = "variants")]
46    pub(crate) fn from_hex(hex: String) -> Self {
47        Self { hex }
48    }
49}
50
51impl fmt::Display for StorageKey {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        formatter.write_str(&self.hex)
54    }
55}
56
57impl fmt::Debug for StorageKey {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(formatter, "StorageKey({})", self.hex)
60    }
61}
62
63sofie_core::text_value!(StorageKey, hex);
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn a_generated_key_parses_back_and_differs_from_the_next() {
71        let key = StorageKey::generate().unwrap();
72
73        assert_eq!(StorageKey::parse(key.as_str()).unwrap(), key);
74        assert_ne!(StorageKey::generate().unwrap(), key);
75    }
76
77    #[test]
78    fn only_32_lowercase_hex_digits_are_a_key() {
79        for text in [
80            "",
81            "../../etc/passwd",
82            "0123456789abcdef0123456789ABCDEF",
83            "0123456789abcdef0123456789abcde",
84            "0123456789abcdef0123456789abcdef0",
85            "0123456789abcdef/123456789abcdef",
86        ] {
87            assert!(StorageKey::parse(text).is_err(), "{text:?}");
88        }
89        assert!(StorageKey::parse("0123456789abcdef0123456789abcdef").is_ok());
90    }
91}