cryptid_rs/
config.rs

1use once_cell::sync::Lazy;
2use std::sync::Mutex;
3
4use crate::field::clear_codec_cache;
5
6static GLOBAL_CONFIG: Lazy<Mutex<Option<Config>>> = Lazy::new(|| Mutex::new(None));
7
8thread_local! {
9    static THREAD_LOCAL_CONFIG: std::cell::RefCell<Option<Config>> =
10        const { std::cell::RefCell::new(None) };
11}
12
13/// Configuring the cryptid library.
14#[derive(Clone)]
15pub struct Config {
16    pub(crate) hmac_length: u8,
17    pub(crate) key: Vec<u8>,
18    pub(crate) zero_pad_length: u8,
19}
20
21#[derive(Debug)]
22pub enum ConfigError {
23    InvalidMacLength,
24    InvalidVersion,
25    InvalidZeroPadLength,
26}
27
28impl Config {
29    /// Creates a new configuration with the given master `key` and other settings in
30    /// default values.
31    /// - `mac_length` defaults to 4, which is large enough to make guessing impractical
32    ///   but still keeps the strings relatively short. High security applications may want
33    ///   to use a higher value.
34    /// - `zero_pad_length` defaults to 4, which is large enough for most applications
35    ///   to never see encoded strings increase in size, while still keeping the strings
36    ///   relatively short.
37    pub fn new(key: &[u8]) -> Self {
38        Config {
39            hmac_length: 4,
40            key: key.to_vec(),
41            zero_pad_length: 4,
42        }
43    }
44
45    /// Sets the number of bytes in the HMAC.
46    /// The value must be between 0 and 8.
47    pub fn hmac_length(mut self, hmac_length: u8) -> Result<Self, ConfigError> {
48        if hmac_length > 8 {
49            Err(ConfigError::InvalidMacLength)
50        } else {
51            self.hmac_length = hmac_length;
52            Ok(self)
53        }
54    }
55
56    /// Sets the number of bytes to zero-pad numbers before encoding.
57    /// The value must be between 0 and 8.
58    pub fn zero_pad_length(mut self, zero_pad_length: u8) -> Result<Self, ConfigError> {
59        if zero_pad_length > 8 {
60            Err(ConfigError::InvalidZeroPadLength)
61        } else {
62            self.zero_pad_length = zero_pad_length;
63            Ok(self)
64        }
65    }
66
67    /// Sets the global configuration. This should be called before the `Field` type methods
68    /// are called.
69    pub fn set_global(config: Config) {
70        let mut global_config = GLOBAL_CONFIG.lock().unwrap();
71        *global_config = Some(config);
72    }
73
74    /// Sets the thread-local configuration. This takes precedence over the global configuration
75    /// for the current thread only.
76    pub fn set_thread_local(config: Config) {
77        THREAD_LOCAL_CONFIG.with(|tl_config| {
78            *tl_config.borrow_mut() = Some(config);
79        });
80        clear_codec_cache();
81    }
82
83    /// Clears the thread-local configuration for the current thread.
84    pub fn clear_thread_local() {
85        THREAD_LOCAL_CONFIG.with(|tl_config| {
86            *tl_config.borrow_mut() = None;
87        });
88        clear_codec_cache();
89    }
90
91    /// Accesses the effective configuration, checking thread-local first, then global.
92    /// Thread-local configuration takes precedence over global configuration.
93    pub fn effective() -> Option<Config> {
94        // Check thread-local first
95        let thread_local = THREAD_LOCAL_CONFIG.with(|tl_config| tl_config.borrow().clone());
96
97        if thread_local.is_some() {
98            thread_local
99        } else {
100            // Fall back to global
101            GLOBAL_CONFIG.lock().unwrap().clone()
102        }
103    }
104
105    /// Accesses the global configuration, if set.
106    ///
107    /// **Note**: Consider using `Config::effective()` instead, which checks
108    /// thread-local configuration first.
109    pub fn global() -> Option<Config> {
110        GLOBAL_CONFIG.lock().unwrap().clone()
111    }
112}