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}