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)]
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
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::oid;
145
146    #[test]
147    fn test_oid_table_insert_and_get() {
148        let mut table: OidTable<i32> = OidTable::new();
149
150        table.insert(oid!(1, 3, 6, 1, 2), 100);
151        table.insert(oid!(1, 3, 6, 1, 1), 50);
152        table.insert(oid!(1, 3, 6, 1, 3), 150);
153
154        // Should maintain sorted order
155        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&50));
156        assert_eq!(table.get(&oid!(1, 3, 6, 1, 2)), Some(&100));
157        assert_eq!(table.get(&oid!(1, 3, 6, 1, 3)), Some(&150));
158        assert_eq!(table.get(&oid!(1, 3, 6, 1, 4)), None);
159    }
160
161    #[test]
162    fn test_oid_table_update_existing() {
163        let mut table: OidTable<i32> = OidTable::new();
164
165        table.insert(oid!(1, 3, 6, 1, 1), 50);
166        table.insert(oid!(1, 3, 6, 1, 1), 100);
167
168        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&100));
169        assert_eq!(table.len(), 1);
170    }
171
172    #[test]
173    fn test_oid_table_get_next() {
174        let mut table: OidTable<i32> = OidTable::new();
175
176        table.insert(oid!(1, 3, 6, 1, 1), 50);
177        table.insert(oid!(1, 3, 6, 1, 2), 100);
178        table.insert(oid!(1, 3, 6, 1, 3), 150);
179
180        // Before first
181        let next = table.get_next(&oid!(1, 3, 6, 1, 0));
182        assert!(next.is_some());
183        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 1));
184
185        // Exact match returns next
186        let next = table.get_next(&oid!(1, 3, 6, 1, 1));
187        assert!(next.is_some());
188        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
189
190        // Between entries
191        let next = table.get_next(&oid!(1, 3, 6, 1, 1, 5));
192        assert!(next.is_some());
193        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
194
195        // After last
196        let next = table.get_next(&oid!(1, 3, 6, 1, 3));
197        assert!(next.is_none());
198
199        let next = table.get_next(&oid!(1, 3, 6, 1, 4));
200        assert!(next.is_none());
201    }
202
203    #[test]
204    fn test_oid_table_remove() {
205        let mut table: OidTable<i32> = OidTable::new();
206
207        table.insert(oid!(1, 3, 6, 1, 1), 50);
208        table.insert(oid!(1, 3, 6, 1, 2), 100);
209
210        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), Some(50));
211        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), None);
212        assert_eq!(table.len(), 1);
213    }
214
215    #[test]
216    fn test_oid_table_iter() {
217        let mut table: OidTable<i32> = OidTable::new();
218
219        table.insert(oid!(1, 3, 6, 1, 3), 150);
220        table.insert(oid!(1, 3, 6, 1, 1), 50);
221        table.insert(oid!(1, 3, 6, 1, 2), 100);
222
223        let entries: Vec<_> = table.iter().collect();
224        assert_eq!(entries.len(), 3);
225        assert_eq!(entries[0].0, &oid!(1, 3, 6, 1, 1));
226        assert_eq!(entries[1].0, &oid!(1, 3, 6, 1, 2));
227        assert_eq!(entries[2].0, &oid!(1, 3, 6, 1, 3));
228    }
229
230    #[test]
231    fn test_oid_table_empty() {
232        let table: OidTable<i32> = OidTable::new();
233        assert!(table.is_empty());
234        assert_eq!(table.len(), 0);
235        assert!(table.get_next(&oid!(1, 3, 6, 1)).is_none());
236    }
237}