1use alloc::string::String;
32use alloc::vec::Vec;
33
34use crate::access_control::Decision;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum BackendResult {
40 Created(String),
43 Deleted,
45 List(Vec<String>),
47 Body {
49 content_type: String,
51 body: Vec<u8>,
53 },
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum BackendError {
59 NotFound(String),
61 Conflict(String),
63 BadRequest(String),
65 Forbidden,
67 Internal(String),
69}
70
71pub trait DdsBackend {
78 fn create_application(&mut self, app_name: &str) -> Result<BackendResult, BackendError>;
85
86 fn delete_application(&mut self, app_name: &str) -> Result<BackendResult, BackendError>;
91
92 fn list_applications(&self, pattern: &str) -> Result<BackendResult, BackendError>;
97
98 fn create_participant(
105 &mut self,
106 app_name: &str,
107 domain_id: u32,
108 ) -> Result<BackendResult, BackendError>;
109
110 fn delete_participant(
115 &mut self,
116 app_name: &str,
117 participant: &str,
118 ) -> Result<BackendResult, BackendError>;
119
120 fn create_topic(
127 &mut self,
128 app_name: &str,
129 participant: &str,
130 topic_name: &str,
131 type_name: &str,
132 ) -> Result<BackendResult, BackendError>;
133
134 fn create_data_writer(
141 &mut self,
142 app_name: &str,
143 participant: &str,
144 publisher: &str,
145 topic: &str,
146 ) -> Result<BackendResult, BackendError>;
147
148 fn create_data_reader(
153 &mut self,
154 app_name: &str,
155 participant: &str,
156 subscriber: &str,
157 topic: &str,
158 ) -> Result<BackendResult, BackendError>;
159
160 fn write_sample(
167 &mut self,
168 app_name: &str,
169 participant: &str,
170 publisher: &str,
171 writer: &str,
172 body: &[u8],
173 ) -> Result<BackendResult, BackendError>;
174
175 fn read_samples(
180 &mut self,
181 app_name: &str,
182 participant: &str,
183 subscriber: &str,
184 reader: &str,
185 selector: Option<&str>,
186 ) -> Result<BackendResult, BackendError>;
187}
188
189pub fn enforce(decision: Decision) -> Result<(), BackendError> {
198 match decision {
199 Decision::Permit => Ok(()),
200 Decision::Deny => Err(BackendError::Forbidden),
201 }
202}
203
204#[cfg(test)]
205#[allow(clippy::expect_used)]
206mod tests {
207 use super::*;
208 use crate::access_control::{Decision, Operation, Permissions, Rule};
209 use alloc::collections::BTreeMap;
210
211 #[derive(Default)]
214 struct InMemoryBackend {
215 apps: BTreeMap<String, ()>,
216 }
217
218 impl DdsBackend for InMemoryBackend {
219 fn create_application(&mut self, n: &str) -> Result<BackendResult, BackendError> {
220 if self.apps.contains_key(n) {
221 return Err(BackendError::Conflict(n.to_string()));
222 }
223 self.apps.insert(n.to_string(), ());
224 Ok(BackendResult::Created(alloc::format!("/applications/{n}")))
225 }
226 fn delete_application(&mut self, n: &str) -> Result<BackendResult, BackendError> {
227 if self.apps.remove(n).is_none() {
228 return Err(BackendError::NotFound(n.to_string()));
229 }
230 Ok(BackendResult::Deleted)
231 }
232 fn list_applications(&self, _p: &str) -> Result<BackendResult, BackendError> {
233 Ok(BackendResult::List(self.apps.keys().cloned().collect()))
234 }
235 fn create_participant(&mut self, _: &str, _: u32) -> Result<BackendResult, BackendError> {
236 Ok(BackendResult::Created("p".to_string()))
237 }
238 fn delete_participant(&mut self, _: &str, _: &str) -> Result<BackendResult, BackendError> {
239 Ok(BackendResult::Deleted)
240 }
241 fn create_topic(
242 &mut self,
243 _: &str,
244 _: &str,
245 _: &str,
246 _: &str,
247 ) -> Result<BackendResult, BackendError> {
248 Ok(BackendResult::Created("t".to_string()))
249 }
250 fn create_data_writer(
251 &mut self,
252 _: &str,
253 _: &str,
254 _: &str,
255 _: &str,
256 ) -> Result<BackendResult, BackendError> {
257 Ok(BackendResult::Created("dw".to_string()))
258 }
259 fn create_data_reader(
260 &mut self,
261 _: &str,
262 _: &str,
263 _: &str,
264 _: &str,
265 ) -> Result<BackendResult, BackendError> {
266 Ok(BackendResult::Created("dr".to_string()))
267 }
268 fn write_sample(
269 &mut self,
270 _: &str,
271 _: &str,
272 _: &str,
273 _: &str,
274 _: &[u8],
275 ) -> Result<BackendResult, BackendError> {
276 Ok(BackendResult::Deleted)
277 }
278 fn read_samples(
279 &mut self,
280 _: &str,
281 _: &str,
282 _: &str,
283 _: &str,
284 _: Option<&str>,
285 ) -> Result<BackendResult, BackendError> {
286 Ok(BackendResult::List(Vec::new()))
287 }
288 }
289
290 #[test]
291 fn enforce_permit_allows_call() {
292 assert!(enforce(Decision::Permit).is_ok());
293 }
294
295 #[test]
296 fn enforce_deny_returns_forbidden() {
297 let r = enforce(Decision::Deny);
298 assert!(matches!(r, Err(BackendError::Forbidden)));
299 }
300
301 #[test]
302 fn permission_check_then_backend_call_chains_correctly() {
303 let perms = Permissions {
304 subject_name: "alice".to_string(),
305 default: Decision::Deny,
306 rules: alloc::vec![Rule::allow("*", alloc::vec![Operation::Admin])],
307 };
308 let mut backend = InMemoryBackend::default();
309 let dec = perms.evaluate(Operation::Admin, "App1");
311 assert!(enforce(dec).is_ok());
312 let r = backend.create_application("App1").expect("ok");
313 assert!(matches!(r, BackendResult::Created(_)));
314 }
315
316 #[test]
317 fn create_then_delete_application_round_trip() {
318 let mut b = InMemoryBackend::default();
319 b.create_application("X").expect("ok");
320 let r = b.delete_application("X").expect("ok");
321 assert_eq!(r, BackendResult::Deleted);
322 }
323
324 #[test]
325 fn create_application_twice_yields_conflict() {
326 let mut b = InMemoryBackend::default();
327 b.create_application("X").expect("ok");
328 let err = b.create_application("X").expect_err("conflict");
329 assert!(matches!(err, BackendError::Conflict(_)));
330 }
331}