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    #[must_use]
58    pub fn new() -> Self {
59        Self {
60            entries: Vec::new(),
61        }
62    }
63
64    /// Create an OID table with pre-allocated capacity.
65    #[must_use]
66    pub fn with_capacity(capacity: usize) -> Self {
67        Self {
68            entries: Vec::with_capacity(capacity),
69        }
70    }
71
72    /// Insert an OID-value pair, maintaining sorted order.
73    ///
74    /// If the OID already exists, its value is replaced.
75    pub fn insert(&mut self, oid: Oid, value: V) {
76        match self.entries.binary_search_by(|(o, _)| o.cmp(&oid)) {
77            Ok(idx) => self.entries[idx].1 = value,
78            Err(idx) => self.entries.insert(idx, (oid, value)),
79        }
80    }
81
82    /// Remove an OID from the table.
83    ///
84    /// Returns the removed value if the OID was present.
85    pub fn remove(&mut self, oid: &Oid) -> Option<V> {
86        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
87            Ok(idx) => Some(self.entries.remove(idx).1),
88            Err(_) => None,
89        }
90    }
91
92    /// Get the value for an exact OID match.
93    #[must_use]
94    pub fn get(&self, oid: &Oid) -> Option<&V> {
95        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
96            Ok(idx) => Some(&self.entries[idx].1),
97            Err(_) => None,
98        }
99    }
100
101    /// Get the lexicographically next OID and value after the given OID.
102    ///
103    /// Returns `None` if there are no OIDs greater than the given one.
104    #[must_use]
105    pub fn get_next(&self, oid: &Oid) -> Option<(&Oid, &V)> {
106        match self.entries.binary_search_by(|(o, _)| o.cmp(oid)) {
107            Ok(idx) => {
108                // Exact match, return the next one
109                self.entries.get(idx + 1).map(|(o, v)| (o, v))
110            }
111            Err(idx) => {
112                // No exact match, return the entry at insertion point
113                self.entries.get(idx).map(|(o, v)| (o, v))
114            }
115        }
116    }
117
118    /// Get the number of entries in the table.
119    #[must_use]
120    pub fn len(&self) -> usize {
121        self.entries.len()
122    }
123
124    /// Check if the table is empty.
125    #[must_use]
126    pub fn is_empty(&self) -> bool {
127        self.entries.is_empty()
128    }
129
130    /// Clear all entries from the table.
131    pub fn clear(&mut self) {
132        self.entries.clear();
133    }
134
135    /// Iterate over all OID-value pairs in lexicographic order.
136    pub fn iter(&self) -> impl Iterator<Item = (&Oid, &V)> {
137        self.entries.iter().map(|(o, v)| (o, v))
138    }
139}
140
141impl<V> Default for OidTable<V> {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl<'a, V> IntoIterator for &'a OidTable<V> {
148    type Item = (&'a Oid, &'a V);
149    type IntoIter =
150        std::iter::Map<std::slice::Iter<'a, (Oid, V)>, fn(&'a (Oid, V)) -> (&'a Oid, &'a V)>;
151
152    fn into_iter(self) -> Self::IntoIter {
153        self.entries.iter().map(|(o, v)| (o, v))
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::oid;
161
162    #[test]
163    fn test_oid_table_insert_and_get() {
164        let mut table: OidTable<i32> = OidTable::new();
165
166        table.insert(oid!(1, 3, 6, 1, 2), 100);
167        table.insert(oid!(1, 3, 6, 1, 1), 50);
168        table.insert(oid!(1, 3, 6, 1, 3), 150);
169
170        // Should maintain sorted order
171        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&50));
172        assert_eq!(table.get(&oid!(1, 3, 6, 1, 2)), Some(&100));
173        assert_eq!(table.get(&oid!(1, 3, 6, 1, 3)), Some(&150));
174        assert_eq!(table.get(&oid!(1, 3, 6, 1, 4)), None);
175    }
176
177    #[test]
178    fn test_oid_table_update_existing() {
179        let mut table: OidTable<i32> = OidTable::new();
180
181        table.insert(oid!(1, 3, 6, 1, 1), 50);
182        table.insert(oid!(1, 3, 6, 1, 1), 100);
183
184        assert_eq!(table.get(&oid!(1, 3, 6, 1, 1)), Some(&100));
185        assert_eq!(table.len(), 1);
186    }
187
188    #[test]
189    fn test_oid_table_get_next() {
190        let mut table: OidTable<i32> = OidTable::new();
191
192        table.insert(oid!(1, 3, 6, 1, 1), 50);
193        table.insert(oid!(1, 3, 6, 1, 2), 100);
194        table.insert(oid!(1, 3, 6, 1, 3), 150);
195
196        // Before first
197        let next = table.get_next(&oid!(1, 3, 6, 1, 0));
198        assert!(next.is_some());
199        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 1));
200
201        // Exact match returns next
202        let next = table.get_next(&oid!(1, 3, 6, 1, 1));
203        assert!(next.is_some());
204        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
205
206        // Between entries
207        let next = table.get_next(&oid!(1, 3, 6, 1, 1, 5));
208        assert!(next.is_some());
209        assert_eq!(next.unwrap().0, &oid!(1, 3, 6, 1, 2));
210
211        // After last
212        let next = table.get_next(&oid!(1, 3, 6, 1, 3));
213        assert!(next.is_none());
214
215        let next = table.get_next(&oid!(1, 3, 6, 1, 4));
216        assert!(next.is_none());
217    }
218
219    #[test]
220    fn test_oid_table_remove() {
221        let mut table: OidTable<i32> = OidTable::new();
222
223        table.insert(oid!(1, 3, 6, 1, 1), 50);
224        table.insert(oid!(1, 3, 6, 1, 2), 100);
225
226        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), Some(50));
227        assert_eq!(table.remove(&oid!(1, 3, 6, 1, 1)), None);
228        assert_eq!(table.len(), 1);
229    }
230
231    #[test]
232    fn test_oid_table_iter() {
233        let mut table: OidTable<i32> = OidTable::new();
234
235        table.insert(oid!(1, 3, 6, 1, 3), 150);
236        table.insert(oid!(1, 3, 6, 1, 1), 50);
237        table.insert(oid!(1, 3, 6, 1, 2), 100);
238
239        let entries: Vec<_> = table.iter().collect();
240        assert_eq!(entries.len(), 3);
241        assert_eq!(entries[0].0, &oid!(1, 3, 6, 1, 1));
242        assert_eq!(entries[1].0, &oid!(1, 3, 6, 1, 2));
243        assert_eq!(entries[2].0, &oid!(1, 3, 6, 1, 3));
244    }
245
246    #[test]
247    fn test_oid_table_empty() {
248        let table: OidTable<i32> = OidTable::new();
249        assert!(table.is_empty());
250        assert_eq!(table.len(), 0);
251        assert!(table.get_next(&oid!(1, 3, 6, 1)).is_none());
252    }
253}