Skip to main content

zerodds_security/
properties.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Property-Liste — Name/Value-Paare für Plugin-Konfiguration.
5//!
6//! Spec OMG DDS-Security 1.1 §8.2.1 `Property_t` + `PropertyQosPolicy`.
7//! Properties werden beim Participant erzeugt und an die Plugins
8//! durchgereicht (z.B. Zertifikats-Pfade, HSM-URIs, Cipher-Suites).
9
10extern crate alloc;
11
12use alloc::borrow::Cow;
13use alloc::vec::Vec;
14
15/// Ein einzelnes Property: Name + Value.
16///
17/// `propagate=true` wird an Remote-Participants via SEDP gesendet (z.B.
18/// `dds.sec.permissions_hash`); `false` bleibt lokal (z.B.
19/// `dds.sec.auth.private_key_path` — nie ueber die Wire!).
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Property {
22    /// Key (reverse-DNS Convention, `dds.sec.xyz`).
23    pub name: Cow<'static, str>,
24    /// Wert (opaker UTF-8-String; Plugin interpretiert).
25    pub value: Cow<'static, str>,
26    /// `true` → via SEDP propagiert. Default `false` — niemals
27    /// geheimnisvolle Strings an Remote leaken.
28    pub propagate: bool,
29}
30
31impl Property {
32    /// Konstruktor: lokal, nicht propagiert (Default).
33    #[must_use]
34    pub fn local(name: impl Into<Cow<'static, str>>, value: impl Into<Cow<'static, str>>) -> Self {
35        Self {
36            name: name.into(),
37            value: value.into(),
38            propagate: false,
39        }
40    }
41
42    /// Konstruktor: wird via SEDP propagiert.
43    #[must_use]
44    pub fn propagated(
45        name: impl Into<Cow<'static, str>>,
46        value: impl Into<Cow<'static, str>>,
47    ) -> Self {
48        Self {
49            name: name.into(),
50            value: value.into(),
51            propagate: true,
52        }
53    }
54}
55
56/// Liste von Properties. Reihenfolge egal, Namen muessen eindeutig sein
57/// — bei Duplikat gewinnt letzter Eintrag (wie OMG-Spec).
58#[derive(Debug, Clone, Default, PartialEq, Eq)]
59pub struct PropertyList {
60    entries: Vec<Property>,
61}
62
63impl PropertyList {
64    /// Leere Liste.
65    #[must_use]
66    pub fn new() -> Self {
67        Self::default()
68    }
69
70    /// Fuegt ein Property hinzu (oder ersetzt, falls Name schon da).
71    pub fn set(&mut self, p: Property) {
72        if let Some(existing) = self.entries.iter_mut().find(|e| e.name == p.name) {
73            *existing = p;
74        } else {
75            self.entries.push(p);
76        }
77    }
78
79    /// Builder-Variante fuer `set`.
80    #[must_use]
81    pub fn with(mut self, p: Property) -> Self {
82        self.set(p);
83        self
84    }
85
86    /// Liefert den Wert zu einem Namen.
87    #[must_use]
88    pub fn get(&self, name: &str) -> Option<&str> {
89        self.entries
90            .iter()
91            .find(|p| p.name == name)
92            .map(|p| p.value.as_ref())
93    }
94
95    /// Alle Properties (read-only).
96    #[must_use]
97    pub fn entries(&self) -> &[Property] {
98        &self.entries
99    }
100
101    /// Nur propagierbare Properties (fuer SEDP-Wire).
102    pub fn propagatable(&self) -> impl Iterator<Item = &Property> {
103        self.entries.iter().filter(|p| p.propagate)
104    }
105}
106
107#[cfg(test)]
108#[allow(clippy::unwrap_used)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn set_replaces_duplicate() {
114        let mut list = PropertyList::new();
115        list.set(Property::local("k", "v1"));
116        list.set(Property::local("k", "v2"));
117        assert_eq!(list.entries().len(), 1);
118        assert_eq!(list.get("k"), Some("v2"));
119    }
120
121    #[test]
122    fn propagatable_filter() {
123        let list = PropertyList::new()
124            .with(Property::local("secret", "sensitive"))
125            .with(Property::propagated("public", "hashed"));
126        let propagated: Vec<_> = list.propagatable().collect();
127        assert_eq!(propagated.len(), 1);
128        assert_eq!(propagated[0].name, "public");
129    }
130}