Skip to main content

zerodds_corba_poa/
active_object_map.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Active-Object-Map (AOM) — Spec §11.3.5.
5//!
6//! Bei RETAIN-POAs haelt der POA eine bidirektionale Map zwischen
7//! ObjectId und Servant. UNIQUE_ID erlaubt nur 1:1-Mappings;
8//! MULTIPLE_ID erlaubt einem Servant mehrere ObjectIds.
9
10use alloc::boxed::Box;
11use alloc::collections::BTreeMap;
12use alloc::vec::Vec;
13
14use crate::error::{PoaError, PoaResult};
15use crate::object_id::ObjectId;
16use crate::policies::IdUniquenessPolicy;
17use crate::servant::Servant;
18
19/// Eindeutige interne ID pro registriertem Servant — wir brauchen
20/// das, weil `Box<dyn Servant>` keine eindeutige Identitaet hat
21/// (zwei Boxes auf dasselbe Type sind verschieden).
22pub type ServantId = u64;
23
24/// Active-Object-Map.
25#[derive(Default)]
26pub struct ActiveObjectMap {
27    next_servant_id: ServantId,
28    by_oid: BTreeMap<ObjectId, ServantId>,
29    by_servant: BTreeMap<ServantId, Vec<ObjectId>>,
30    servants: BTreeMap<ServantId, Box<dyn Servant>>,
31    uniqueness: IdUniquenessPolicy,
32}
33
34impl core::fmt::Debug for ActiveObjectMap {
35    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36        f.debug_struct("ActiveObjectMap")
37            .field("entries", &self.by_oid.len())
38            .field("servants", &self.servants.len())
39            .field("uniqueness", &self.uniqueness)
40            .finish()
41    }
42}
43
44impl ActiveObjectMap {
45    /// Konstruktor.
46    #[must_use]
47    pub const fn new(uniqueness: IdUniquenessPolicy) -> Self {
48        Self {
49            next_servant_id: 1,
50            by_oid: BTreeMap::new(),
51            by_servant: BTreeMap::new(),
52            servants: BTreeMap::new(),
53            uniqueness,
54        }
55    }
56
57    /// Registriert ein neues Servant unter der gegebenen ObjectId.
58    ///
59    /// # Errors
60    /// * `ObjectAlreadyActive` wenn `oid` schon mappt.
61    /// * `ServantAlreadyActive` (UNIQUE_ID) wenn der Servant schon
62    ///   eine andere ObjectId hat.
63    pub fn activate(&mut self, oid: ObjectId, servant: Box<dyn Servant>) -> PoaResult<ServantId> {
64        if self.by_oid.contains_key(&oid) {
65            return Err(PoaError::ObjectAlreadyActive);
66        }
67        let sid = self.next_servant_id;
68        self.next_servant_id += 1;
69        self.by_oid.insert(oid.clone(), sid);
70        self.by_servant.insert(sid, alloc::vec![oid]);
71        self.servants.insert(sid, servant);
72        Ok(sid)
73    }
74
75    /// Registriert ein bestehendes Servant unter einer zusaetzlichen
76    /// ObjectId (nur unter MULTIPLE_ID erlaubt).
77    ///
78    /// # Errors
79    /// * `ServantNotActive` wenn `sid` unbekannt.
80    /// * `ObjectAlreadyActive` wenn `oid` schon mappt.
81    /// * `ServantAlreadyActive` wenn UNIQUE_ID.
82    pub fn add_alias(&mut self, sid: ServantId, oid: ObjectId) -> PoaResult<()> {
83        if !self.servants.contains_key(&sid) {
84            return Err(PoaError::ServantNotActive);
85        }
86        if self.uniqueness == IdUniquenessPolicy::Unique {
87            return Err(PoaError::ServantAlreadyActive);
88        }
89        if self.by_oid.contains_key(&oid) {
90            return Err(PoaError::ObjectAlreadyActive);
91        }
92        self.by_oid.insert(oid.clone(), sid);
93        self.by_servant.entry(sid).or_default().push(oid);
94        Ok(())
95    }
96
97    /// Liefert den Servant fuer eine ObjectId.
98    #[must_use]
99    pub fn get(&self, oid: &ObjectId) -> Option<&dyn Servant> {
100        let sid = *self.by_oid.get(oid)?;
101        self.servants.get(&sid).map(|s| s.as_ref())
102    }
103
104    /// Liefert die `ServantId` fuer eine ObjectId.
105    #[must_use]
106    pub fn servant_id(&self, oid: &ObjectId) -> Option<ServantId> {
107        self.by_oid.get(oid).copied()
108    }
109
110    /// Liefert die Object-Ids unter denen ein Servant registriert ist.
111    #[must_use]
112    pub fn ids_for_servant(&self, sid: ServantId) -> &[ObjectId] {
113        self.by_servant.get(&sid).map(Vec::as_slice).unwrap_or(&[])
114    }
115
116    /// Deaktiviert eine ObjectId. Wenn der Servant keine weiteren
117    /// Aliasses hat, wird er auch entfernt.
118    ///
119    /// # Errors
120    /// `ObjectNotActive` wenn `oid` nicht mappt.
121    pub fn deactivate(&mut self, oid: &ObjectId) -> PoaResult<Box<dyn Servant>> {
122        let sid = self.by_oid.remove(oid).ok_or(PoaError::ObjectNotActive)?;
123        if let Some(list) = self.by_servant.get_mut(&sid) {
124            list.retain(|i| i != oid);
125            if list.is_empty() {
126                self.by_servant.remove(&sid);
127                let s = self.servants.remove(&sid);
128                return s.ok_or(PoaError::ServantNotActive);
129            }
130        }
131        // Servant haengt noch unter anderen IDs — kein Servant
132        // zurueckgeben (Box noch in Map).
133        Err(PoaError::BadInvocationOrder(
134            "servant still has other aliases".into(),
135        ))
136    }
137
138    /// Anzahl Eintraege.
139    #[must_use]
140    pub fn len(&self) -> usize {
141        self.by_oid.len()
142    }
143
144    /// `true` wenn leer.
145    #[must_use]
146    pub fn is_empty(&self) -> bool {
147        self.by_oid.is_empty()
148    }
149
150    /// Setzt die Uniqueness-Policy (intern, fuer POA-Konstruktion).
151    pub(crate) fn set_uniqueness(&mut self, u: IdUniquenessPolicy) {
152        self.uniqueness = u;
153    }
154}
155
156#[cfg(test)]
157#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
158mod tests {
159    use super::*;
160    use crate::servant::EchoServant;
161
162    fn echo() -> Box<dyn Servant> {
163        Box::new(EchoServant {
164            repo_id: "IDL:demo/Echo:1.0".into(),
165        })
166    }
167
168    #[test]
169    fn activate_and_get_round_trip() {
170        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Unique);
171        let oid = ObjectId::system_id(1);
172        let sid = m.activate(oid.clone(), echo()).unwrap();
173        assert!(m.get(&oid).is_some());
174        assert_eq!(m.servant_id(&oid), Some(sid));
175        assert_eq!(m.len(), 1);
176    }
177
178    #[test]
179    fn activate_twice_with_same_oid_is_rejected() {
180        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Unique);
181        let oid = ObjectId::system_id(1);
182        m.activate(oid.clone(), echo()).unwrap();
183        let err = m.activate(oid, echo()).unwrap_err();
184        assert_eq!(err, PoaError::ObjectAlreadyActive);
185    }
186
187    #[test]
188    fn unique_id_rejects_alias() {
189        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Unique);
190        let sid = m.activate(ObjectId::system_id(1), echo()).unwrap();
191        let err = m.add_alias(sid, ObjectId::system_id(2)).unwrap_err();
192        assert_eq!(err, PoaError::ServantAlreadyActive);
193    }
194
195    #[test]
196    fn multiple_id_allows_aliases() {
197        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Multiple);
198        let sid = m.activate(ObjectId::system_id(1), echo()).unwrap();
199        m.add_alias(sid, ObjectId::system_id(2)).unwrap();
200        m.add_alias(sid, ObjectId::system_id(3)).unwrap();
201        assert_eq!(m.ids_for_servant(sid).len(), 3);
202        assert_eq!(m.len(), 3);
203    }
204
205    #[test]
206    fn deactivate_removes_only_when_last_alias_gone() {
207        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Multiple);
208        let sid = m.activate(ObjectId::system_id(1), echo()).unwrap();
209        m.add_alias(sid, ObjectId::system_id(2)).unwrap();
210
211        // Erste Deaktivierung — Servant haengt noch unter ID 2.
212        let err = m.deactivate(&ObjectId::system_id(1)).unwrap_err();
213        assert!(matches!(err, PoaError::BadInvocationOrder(_)));
214        assert_eq!(m.len(), 1);
215
216        // Zweite Deaktivierung entfernt den Servant.
217        let _s = m.deactivate(&ObjectId::system_id(2)).unwrap();
218        assert_eq!(m.len(), 0);
219    }
220
221    #[test]
222    fn deactivate_unknown_oid_is_diagnostic() {
223        let mut m = ActiveObjectMap::new(IdUniquenessPolicy::Unique);
224        let err = m.deactivate(&ObjectId::system_id(99)).unwrap_err();
225        assert_eq!(err, PoaError::ObjectNotActive);
226    }
227}