aimdb_core/remote/
config.rs1use alloc::string::String;
4use alloc::vec::Vec;
5use hashbrown::HashSet;
6
7use crate::record_id::StringKey;
8
9#[derive(Debug, Clone)]
14pub struct AimxConfig {
15 pub socket_path: String,
17
18 pub security_policy: SecurityPolicy,
20
21 pub max_connections: usize,
26
27 pub max_subs_per_connection: usize,
32
33 pub auth_token: Option<String>,
35
36 pub socket_permissions: Option<u32>,
39}
40
41impl AimxConfig {
42 pub fn uds_default() -> Self {
52 Self {
53 socket_path: String::from("/tmp/aimdb.sock"),
54 security_policy: SecurityPolicy::ReadOnly,
55 max_connections: 16,
56 max_subs_per_connection: 32,
57 auth_token: None,
58 socket_permissions: Some(0o600),
59 }
60 }
61
62 pub fn socket_path(mut self, path: impl Into<String>) -> Self {
64 self.socket_path = path.into();
65 self
66 }
67
68 pub fn security_policy(mut self, policy: SecurityPolicy) -> Self {
70 self.security_policy = policy;
71 self
72 }
73
74 pub fn max_connections(mut self, max: usize) -> Self {
76 self.max_connections = max;
77 self
78 }
79
80 pub fn max_subs_per_connection(mut self, max: usize) -> Self {
82 self.max_subs_per_connection = max;
83 self
84 }
85
86 pub fn auth_token(mut self, token: impl Into<String>) -> Self {
88 self.auth_token = Some(token.into());
89 self
90 }
91
92 pub fn socket_permissions(mut self, mode: u32) -> Self {
101 self.socket_permissions = Some(mode);
102 self
103 }
104}
105
106#[derive(Debug, Clone)]
110pub enum SecurityPolicy {
111 ReadOnly,
116
117 ReadWrite {
122 writable_records: HashSet<String>,
124 },
125}
126
127impl SecurityPolicy {
128 pub fn read_only() -> Self {
130 Self::ReadOnly
131 }
132
133 pub fn read_write() -> Self {
135 Self::ReadWrite {
136 writable_records: HashSet::new(),
137 }
138 }
139
140 #[allow(
144 clippy::panic,
145 reason = "documented API contract; a Result instead is a breaking change"
146 )]
147 pub fn allow_write_key(&mut self, key: impl Into<String>) {
148 match self {
149 Self::ReadWrite { writable_records } => {
150 writable_records.insert(key.into());
151 }
152 Self::ReadOnly => {
153 panic!("Cannot allow writes in ReadOnly security policy");
154 }
155 }
156 }
157
158 #[allow(
163 clippy::panic,
164 reason = "documented API contract; a Result instead is a breaking change"
165 )]
166 pub fn with_writable_key(mut self, key: impl Into<String>) -> Self {
167 match self {
168 Self::ReadWrite {
169 ref mut writable_records,
170 } => {
171 writable_records.insert(key.into());
172 self
173 }
174 Self::ReadOnly => {
175 panic!("Cannot allow writes in ReadOnly security policy");
176 }
177 }
178 }
179
180 pub fn is_writable_key(&self, key: &str) -> bool {
182 match self {
183 Self::ReadOnly => false,
184 Self::ReadWrite { writable_records } => writable_records.contains(key),
185 }
186 }
187
188 pub fn permissions(&self) -> &[&str] {
190 match self {
191 Self::ReadOnly => &["read", "subscribe"],
192 Self::ReadWrite { .. } => &["read", "subscribe", "write"],
193 }
194 }
195
196 pub fn writable_records(&self) -> Vec<String> {
198 match self {
199 Self::ReadOnly => Vec::new(),
200 Self::ReadWrite { writable_records } => writable_records.iter().cloned().collect(),
201 }
202 }
203
204 pub fn writable_record_keys(&self) -> Vec<StringKey> {
206 match self {
207 Self::ReadOnly => Vec::new(),
208 Self::ReadWrite { writable_records } => writable_records
209 .iter()
210 .map(|s| StringKey::from_dynamic(s.as_str()))
211 .collect(),
212 }
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 #[cfg(feature = "std")]
222 fn test_default_config() {
223 let config = AimxConfig::uds_default();
224 assert_eq!(config.socket_path, "/tmp/aimdb.sock");
225 assert_eq!(config.max_connections, 16);
226 assert_eq!(config.max_subs_per_connection, 32);
227 assert!(matches!(config.security_policy, SecurityPolicy::ReadOnly));
228 assert!(config.auth_token.is_none());
229 }
230
231 #[test]
232 #[cfg(feature = "std")]
233 fn test_config_builder() {
234 let config = AimxConfig::uds_default()
235 .socket_path("/var/run/aimdb.sock")
236 .max_connections(32)
237 .max_subs_per_connection(8)
238 .auth_token("secret-token")
239 .socket_permissions(0o660);
240
241 assert_eq!(config.socket_path, "/var/run/aimdb.sock");
242 assert_eq!(config.max_connections, 32);
243 assert_eq!(config.max_subs_per_connection, 8);
244 assert_eq!(config.auth_token, Some("secret-token".to_string()));
245 assert_eq!(config.socket_permissions, Some(0o660));
246 }
247
248 #[test]
249 fn test_security_policy_read_only() {
250 let policy = SecurityPolicy::read_only();
251 assert!(!policy.is_writable_key("test.record"));
252 assert_eq!(policy.permissions(), &["read", "subscribe"]);
253 }
254
255 #[test]
256 fn test_security_policy_read_write() {
257 let mut policy = SecurityPolicy::read_write();
258 assert!(!policy.is_writable_key("test.record"));
259
260 policy.allow_write_key("test.record");
261 assert!(policy.is_writable_key("test.record"));
262 assert!(!policy.is_writable_key("other.record"));
263 assert_eq!(policy.permissions(), &["read", "subscribe", "write"]);
264 }
265
266 #[test]
267 #[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
268 fn test_security_policy_read_only_panic() {
269 let mut policy = SecurityPolicy::read_only();
270 policy.allow_write_key("test.record");
271 }
272
273 #[test]
274 #[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
275 fn test_security_policy_read_only_builder_panic() {
276 let _policy = SecurityPolicy::read_only().with_writable_key("test.record");
277 }
278
279 #[test]
280 fn test_security_policy_builder() {
281 let policy = SecurityPolicy::read_write()
282 .with_writable_key("sensor.temperature")
283 .with_writable_key("config.app");
284
285 assert!(policy.is_writable_key("sensor.temperature"));
286 assert!(policy.is_writable_key("config.app"));
287 assert!(!policy.is_writable_key("other.record"));
288 }
289}