1use std::fmt;
2
3use sofie_core::{Error, Result};
4
5const RANDOM_BYTES: usize = 16;
6const LENGTH: usize = RANDOM_BYTES * 2;
7
8#[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}