Skip to main content

async_snmp/handler/
oid_table.rs

1//! OID table for implementing GETNEXT with sorted OID storage.
2
3use crate::oid::Oid;
4
5/// Helper for implementing GETNEXT with lexicographic OID ordering.
6///
7/// This struct simplifies implementing the `get_next` method of [`MibHandler`](super::MibHandler)
8/// by maintaining a sorted list of OID-value pairs and providing efficient
9/// lookup for the next OID.
10///
11/// # Example
12///
13/// ```rust
14/// use async_snmp::handler::{MibHandler, RequestContext, GetResult, GetNextResult, OidTable, BoxFuture};
15/// use async_snmp::{Oid, Value, VarBind, oid};
16///
17/// struct MyHandler {
18///     table: OidTable<Value>,
19/// }
20///
21/// impl MyHandler {
22///     fn new() -> Self {
23///         let mut table = OidTable::new();
24///         table.insert(oid!(1, 3, 6, 1, 4, 1, 99999, 1, 0), Value::Integer(42));
25///         table.insert(oid!(1, 3, 6, 1, 4, 1, 99999, 2, 0), Value::OctetString("test".into()));
26///         Self { table }
27///     }
28/// }
29///
30/// impl MibHandler for MyHandler {
31///     fn get<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetResult> {
32///         Box::pin(async move {
33///             self.table.get(oid)
34///                 .cloned()
35///                 .map(GetResult::Value)
36///                 .unwrap_or(GetResult::NoSuchObject)
37///         })
38///     }
39///
40///     fn get_next<'a>(&'a self, _ctx: &'a RequestContext, oid: &'a Oid) -> BoxFuture<'a, GetNextResult> {
41///         Box::pin(async move {
42///             self.table.get_next(oid)
43///                 .map(|(next_oid, value)| GetNextResult::Value(VarBind::new(next_oid.clone(), value.clone())))
44///                 .unwrap_or(GetNextResult::EndOfMibView)
45///         })
46///     }
47/// }
48/// ```
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct OidTable<V> {
51    /// Entries are kept sorted by OID for efficient GETNEXT
52    entries: Vec<(Oid, V)>,
53}
54
55impl<V> OidTable<V> {
56    /// Create a new empty OID table.
57    pub fn new() -> Self {
58        Self {
59            entries: Vec::new(),
60        }
61    }
62
63    /// Create an OID table with pre-allocated capacity.
64    pub fn with_capacity(capacity: usize) -> Self {
65        Self {
66            entries: Vec::with_capacity(capacity),
67        }
68    }
69
70    /// Insert an OID-value pair, maintaining sorted order.
71    ///
72    /// If the OID already exists, its value is replaced.
73    pub fn insert(&mut self, oid: Oid, value: V) {
74        match self.entries.binary_search_by(|(o, _)| o.cmp(&oid)) {
75            Ok(idx) => self.entries[idx].1 = value,
76            Err(idx) => self.entries.insert(idx, (oid, value)),
77        }
78    }
79
80    /// Remove an OID from the table.
81    ///
82    /// Returns the removed value if the OID was present.
83    pub fn remove(&mut self, oid: &Oid) -> Option<V> {
84        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
85            Ok(idx) => Some(self.entries.remove(idx).1),
86            Err(_) => None,
87        }
88    }
89
90    /// Get the value for an exact OID match.
91    pub fn get(&self, oid: &Oid) -> Option<&V> {
92        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
93            Ok(idx) => Some(&self.entries[idx].1),
94            Err(_) => None,
95        }
96    }
97
98    /// Get the lexicographically next OID and value after the given OID.
99    ///
100    /// Returns `None` if there are no OIDs greater than the given one.
101    pub fn get_next(&self, oid: &Oid) -> Option<(&Oid, &V)> {
102        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
103            Ok(idx) => {
104                // Exact match, return the next one
105                self.entries.get(idx + 1).map(|(o, v)| (o, v))
106            }
107            Err(idx) => {
108                // No exact match, return the entry at insertion point
109                self.entries.get(idx).map(|(o, v)| (o, v))
110            }
111        }
112    }
113
114    /// Get the number of entries in the table.
115    pub fn len(&self) -> usize {
116        self.entries.len()
117    }
118
119    /// Check if the table is empty.
120    pub fn is_empty(&self) -> bool {
121        self.entries.is_empty()
122    }
123
124    /// Clear all entries from the table.
125    pub fn clear(&mut self) {
126        self.entries.clear();
127    }
128
129    /// Iterate over all OID-value pairs in lexicographic order.
130    pub fn iter(&self) -> impl Iterator<Item = (&Oid, &V)> {
131        self.entries.iter().map(|(o, v)| (o, v))
132    }
133}
134
135impl<V> Default for OidTable<V> {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl<'a, V> IntoIterator for &'a OidTable<V> {
142    type Item = (&'a Oid, &'a V);
143    type IntoIter =
144        std::iter::Map<std::slice::Iter<'a, (Oid, V)>, fn(&'a (Oid, V)) -> (&'a Oid, &'a V)>;
145
146    fn into_iter(self) -> Self::IntoIter {
147        self.entries.iter().map(|(o, v)| (o, v))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::oid;
155
156    #[test]
157    fn test_oid_table_insert_and_get() {
158        let mut table: OidTable<i32> = OidTable::new();
159
160        table.insert(oid!(1, 3, 6, 1, 2), 100);
161        table.insert(oid!(1, 3, 6, 1, 1), 50);
162        table.insert(oid!(1, 3, 6, 1, 3), 150);
163
164        // Should maintain sorted order
165        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&50));
166        assert_eq!(table.get(&oid!(1, 3, 6, 1, 2)), Some(&100));
167        assert_eq!(table.get(&oid!(1, 3, 6, 1, 3)), Some(&150));
168        assert_eq!(table.get(&oid!(1, 3, 6, 1, 4)), None);
169    }
170
171    #[test]
172    fn test_oid_table_update_existing() {
173        let mut table: OidTable<i32> = OidTable::new();
174
175        table.insert(oid!(1, 3, 6, 1, 1), 50);
176        table.insert(oid!(1, 3, 6, 1, 1), 100);
177
178        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&100));
179        assert_eq!(table.len(), 1);
180    }
181
182    #[test]
183    fn test_oid_table_get_next() {
184        let mut table: OidTable<i32> = OidTable::new();
185
186        table.insert(oid!(1, 3, 6, 1, 1), 50);
187        table.insert(oid!(1, 3, 6, 1, 2), 100);
188        table.insert(oid!(1, 3, 6, 1, 3), 150);
189
190        // Before first
191        let next = table.get_next(&oid!(1, 3, 6, 1, 0));
192        assert!(next.is_some());
193        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 1));
194
195        // Exact match returns next
196        let next = table.get_next(&oid!(1, 3, 6, 1, 1));
197        assert!(next.is_some());
198        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
199
200        // Between entries
201        let next = table.get_next(&oid!(1, 3, 6, 1, 1, 5));
202        assert!(next.is_some());
203        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
204
205        // After last
206        let next = table.get_next(&oid!(1, 3, 6, 1, 3));
207        assert!(next.is_none());
208
209        let next = table.get_next(&oid!(1, 3, 6, 1, 4));
210        assert!(next.is_none());
211    }
212
213    #[test]
214    fn test_oid_table_remove() {
215        let mut table: OidTable<i32> = OidTable::new();
216
217        table.insert(oid!(1, 3, 6, 1, 1), 50);
218        table.insert(oid!(1, 3, 6, 1, 2), 100);
219
220        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), Some(50));
221        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), None);
222        assert_eq!(table.len(), 1);
223    }
224
225    #[test]
226    fn test_oid_table_iter() {
227        let mut table: OidTable<i32> = OidTable::new();
228
229        table.insert(oid!(1, 3, 6, 1, 3), 150);
230        table.insert(oid!(1, 3, 6, 1, 1), 50);
231        table.insert(oid!(1, 3, 6, 1, 2), 100);
232
233        let entries: Vec<_> = table.iter().collect();
234        assert_eq!(entries.len(), 3);
235        assert_eq!(entries[0].0, &oid!(1, 3, 6, 1, 1));
236        assert_eq!(entries[1].0, &oid!(1, 3, 6, 1, 2));
237        assert_eq!(entries[2].0, &oid!(1, 3, 6, 1, 3));
238    }
239
240    #[test]
241    fn test_oid_table_empty() {
242        let table: OidTable<i32> = OidTable::new();
243        assert!(table.is_empty());
244        assert_eq!(table.len(), 0);
245        assert!(table.get_next(&oid!(1, 3, 6, 1)).is_none());
246    }
247}