snmp_rust_agent 0.4.0

A framework for building SNMP v3 Agents
Documentation
//! Type used to hold mapping between ObjectIdentifiers and instances that support OidKeep.
//!
//! This simplistic implementation uses a sorted vector of tuples of OID and T
//!
//! You could use a Btree, but search is still O(logN)
//! Hashmap has lookup in O(1) but makes next_oid lookups harder
//!
//! Trie might be worth trying in the future, as lookups are O(1), but as most MIBs are small,
//! logN is about 5 or 6 typically, so the speed up is modest, and real back end operations
//! like system calls are vastly slower. Also, with Trie, it is hard reconstructing key values,
//! likely to need an allocation to get the Oid back.
use crate::keeper::OidKeeper;
use log::info;
use rasn::types::ObjectIdentifier;

/// Mapping between OIDs and trait objects that keep the associated data.
pub struct OidMap {
    store: Vec<(ObjectIdentifier, Box<dyn OidKeeper>)>,
}

impl OidMap {
    /// Return a new empty OidMap
    pub fn new() -> Self {
        let store: Vec<(ObjectIdentifier, Box<dyn OidKeeper>)> = vec![];
        OidMap { store }
    }

    /// Insert OID and associated trait object.
    pub fn push(&mut self, oid: ObjectIdentifier, arg: Box<dyn OidKeeper>) {
        self.store.push((oid, arg));
    }

    /// Sort into OID order, so binary search will work.
    ///
    /// Agent does this just before it enters its read loop
    pub fn sort(&mut self) {
        self.store.sort_by(|a, b| a.0.cmp(&b.0));
        info!("Sorted");
    }

    /// Binary search for trait object associated with oid, or insert point if not exact match
    pub fn search(&self, oid: &ObjectIdentifier) -> Result<usize, usize> {
        self.store.binary_search_by(|a| {
            let al = a.0.len();
            let b = if oid.len() > al {
                oid.get(0..al).unwrap() // Checked, because we know al is less than oid length
            } else {
                oid
            };
            a.0.to_vec().cmp(&b.to_vec())
        })
    }

    /// Return trait object that owns next key after oid, if found.
    pub fn search_next(&mut self, oid: &ObjectIdentifier) -> Option<&mut Box<dyn OidKeeper>> {
        let bin_res = self.store.binary_search_by(|a| {
            let al = a.0.len();
            let b = if oid.len() > al {
                oid.get(0..al).unwrap() // Checked, because we know al is less than oid length
            } else {
                oid
            };
            a.0.to_vec().cmp(&b.to_vec())
        });
        match bin_res {
            Ok(which) => {
                if which < self.store.len() - 1 {
                    Some(&mut self.store[which + 1].1)
                } else {
                    None
                }
            }
            Err(insert_point) => {
                if insert_point < self.store.len() {
                    Some(&mut self.store[insert_point].1)
                } else {
                    None
                }
            }
        }
    }

    /// Look up trait object by integer index
    pub fn idx(&mut self, i: usize) -> &mut Box<dyn OidKeeper> {
        &mut self.store[i].1
    }

    /// Look up Oid by integer index
    pub fn oid(&self, i: usize) -> &ObjectIdentifier {
        &self.store[i].0
    }

    /// Return number of entries in store.
    ///
    /// Note that if some of these are tables, with potentially many rows,
    /// there can be many more valid oid values.
    ///
    /// You can also associate a single trait objects with multiple Oid values.
    ///
    /// Sigh.
    pub fn len(&self) -> usize {
        self.store.len()
    }

    /// True if the store is empty.
    pub fn is_empty(&self) -> bool {
        self.store.is_empty()
    }
}

/// Return an empty OidMap
impl Default for OidMap {
    fn default() -> Self {
        Self::new()
    }
}

/// Mapping between context names and OidMaps
pub struct ContextMap<'a> {
    cmap: Vec<(&'a [u8], &'a mut OidMap)>,
}

impl<'a> ContextMap<'a> {
    pub fn new() -> Self {
        ContextMap { cmap: vec![] }
    }

    /// Insert an context name and its assocaited OidMap
    pub fn insert(&mut self, context_name: &'a [u8], oid_map: &'a mut OidMap) {
        self.cmap.push((context_name, oid_map));
    }

    /// Lookup which OidMap, if any, is associated with the context name
    ///
    /// Could change to binary search if there was a use case requiring many contexts,
    /// but common case is one or two.
    pub fn lookup(&mut self, context_name: &[u8]) -> Option<&mut OidMap> {
        for (name, oid_map) in &mut self.cmap {
            if *name == context_name {
                return Some(oid_map);
            }
        }
        None
    }

    /// Sort the store by context name order, and sort the individual OidMaps by Oid order
    pub fn sort(&mut self) {
        for (_, omap) in &mut self.cmap {
            omap.sort();
        }
        self.cmap.sort_by(|a, b| a.0.cmp(b.0));
    }
}

// Return an empty ContextMap
impl Default for ContextMap<'_> {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::keeper;
    use crate::scalar::ScalarMemOid;
    use crate::utils::*;
    use rasn::types::ObjectIdentifier;
    use rasn_snmp::v3::VarBindValue;

    const ARC: [u32; 1] = [1];
    const ARC_LONG: [u32; 3] = [1, 0, 0];
    const ARC0: [u32; 1] = [0];
    const ARC2: [u32; 2] = [2, 2];
    const ARC3: [u32; 2] = [1, 3];

    #[test]
    fn test_load1() {
        let value = simple_from_int(42);
        let s: Box<dyn OidKeeper> = Box::new(ScalarMemOid::new(
            value.clone(),
            keeper::OType::Integer,
            keeper::Access::ReadWrite,
        ));
        let t: Box<dyn OidKeeper> = Box::new(ScalarMemOid::new(
            value.clone(),
            keeper::OType::Integer,
            keeper::Access::ReadWrite,
        ));
        let o0 = ObjectIdentifier::new(&ARC0).unwrap(); // Checked #[test]
        let o1 = ObjectIdentifier::new(&ARC).unwrap(); // Checked #[test]
        let o1_l = ObjectIdentifier::new(&ARC_LONG).unwrap(); // Checked #[test]
        let o2 = ObjectIdentifier::new(&ARC2).unwrap(); // Checked #[test]
        let o3 = ObjectIdentifier::new(&ARC3).unwrap(); // Checked #[test]
        let mut om = OidMap::default();
        assert!(om.is_empty());
        om.push(o1.clone(), s);
        om.push(o3.clone(), t);
        om.sort();
        assert!(!om.is_empty());
        assert_eq!(om.len(), 2);
        let res = om.search(&o1);
        assert!(res.is_ok());
        let res = om.search(&o2);
        assert!(res.is_err());
        //assert_eq!(om.idx(0), s);
        assert_eq!(*om.oid(0), o1);
        let resn = om.search_next(&o3);
        assert!(resn.is_none());
        let resn = om.search_next(&o2);
        assert!(resn.is_none());
        let resn = om.search_next(&o1);
        assert!(resn.is_some());
        let resn = om.search_next(&o1_l);
        assert!(resn.is_some());
        let resn = om.search_next(&o0);
        assert!(resn.is_some());
        assert_eq!(resn.unwrap().get(o1).unwrap(), VarBindValue::Value(value));
    }

    #[test]
    fn test_context_map() {
        let value = simple_from_int(42);
        let s: Box<dyn OidKeeper> = Box::new(ScalarMemOid::new(
            value,
            keeper::OType::Integer,
            keeper::Access::ReadWrite,
        ));
        let o1 = ObjectIdentifier::new(&ARC).unwrap(); // Checked #[test]
        let mut om = OidMap::default();
        assert!(om.is_empty());
        om.push(o1.clone(), s);
        om.sort();
        assert!(!om.is_empty());
        let mut om2 = OidMap::default();
        {
            let mut cm = ContextMap::default();
            cm.insert(b"test", &mut om);
            cm.insert(b"aardvark", &mut om2);
            cm.sort();
            assert!(cm.lookup(b"other").is_none());
            assert!(cm.lookup(b"test").is_some());
        }
    }
}