use log::{debug, error}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::error::Error; use std::fmt; use std::io; use std::num::ParseIntError; use std::str::FromStr; #[derive(Debug, PartialEq)] pub enum OidError { PrefixMissmatch, PrefixTooLong, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Oid(Vec); impl FromStr for Oid { type Err = ParseIntError; fn from_str(s: &str) -> Result { let r = s .split('.') .filter(|&s| !s.is_empty()) .map(|s| s.parse::()) .collect(); match r { Ok(v) => Ok(Oid(v)), Err(e) => Err(e), } } } impl fmt::Display for Oid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, ".{}", self.0.iter().map(|&n| n.to_string()).collect::>().join(".") ) } } impl Oid { pub fn add_prefix(&self, o: &Oid) -> Oid { let mut x = o.clone(); x.0.extend(&self.0); x } /* pub fn add_suffix(&self, o: &Oid) -> Oid { let mut x = self.clone(); x.0.extend(o.clone().0); x } */ pub fn add_suffix_int(&self, o: u32) -> Oid { let mut x = self.clone(); x.0.push(o); x } pub fn strip_prefix(&self, o: &Oid) -> Result { let pl = o.0.len(); if self.0.len() > pl { let mut x = self.0.clone(); let y = x.split_off(pl); if x == o.0 { return Ok(Oid(y)); } else { return Err(OidError::PrefixMissmatch); } } Err(OidError::PrefixTooLong) } } #[derive(Deserialize, Serialize, Debug)] pub enum SnmpData { String(String), Gauge(u32), Integer(u32), Counter32(u32), Counter64(u64), // More data types ToDo ... //INTEGER //OCTET //IPADDRESS //TIMETICKS //OBJECTID // coding examples see https://github.com/nagius/snmp_passpersist/blob/master/snmp_passpersist.py } impl fmt::Display for SnmpData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(s) => write!(f, "string\n{}", s), Self::Gauge(i) => write!(f, "gauge\n{}", i), Self::Integer(i) => write!(f, "integer\n{}", i), Self::Counter32(i) => write!(f, "counter32\n{}", i), Self::Counter64(i) => write!(f, "counter64\n{}", i), } } } #[derive(Deserialize, Serialize, Debug)] pub struct OidData { pub(crate) base: Oid, pub(crate) data: BTreeMap, } impl OidData { /* pub fn new(base_oid: &str) -> Self { Self { base: base_oid.parse().expect("Unable to parse Oid"), data: BTreeMap::new(), } } */ pub fn new_oid(base_oid: &Oid) -> Self { Self { base: base_oid.clone(), data: BTreeMap::new(), } } pub fn data_lookup(&self, key: &Oid) -> Option<&SnmpData> { self.data.get(key) } pub fn data_lookup_next(&self, key: &Oid) -> Option<(&Oid, &SnmpData)> { for (k, v) in self.data.iter() { if k > key { return Some((k, v)); }; } None } } #[derive(Deserialize, Serialize, Debug)] pub enum SnmpCommands { Ping, Get(Oid), GetNext(Oid), Set(Oid, String), Quit, } impl fmt::Display for SnmpCommands { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { match self { Self::Get(s) => write!(f, "GET => {}", s), Self::GetNext(s) => write!(f, "GETNEXT => {}", s), Self::Ping => write!(f, "PING"), Self::Set(o, s) => write!(f, "SET => {} => {}", o, s), Self::Quit => write!(f, "QUIT"), } } } impl SnmpCommands { pub fn parse_command() -> Result> { let mut cmd = String::new(); io::stdin().read_line(&mut cmd).expect("Failed to read line"); cmd = cmd.to_lowercase().trim().to_string(); let pc: SnmpCommands = match &cmd as &str { "quit" => SnmpCommands::Quit, "ping" => SnmpCommands::Ping, "set" => { let mut setoid = String::new(); let mut setdata = String::new(); // we need to read 2 lines of addional input after SET command, first OID, second io::stdin().read_line(&mut setoid).expect("Failed to read line"); io::stdin().read_line(&mut setdata).expect("Failed to read line"); let oid: Oid = match setoid.trim().to_string().parse() { Ok(v) => v, Err(_e) => { let mystr = format!("unable to parse {} into Oid", setoid.trim()); error!("{}", mystr); return Err(mystr.into()); } }; SnmpCommands::Set(oid, format!("DATA: {}", setdata.trim())) } "get" => { let mut getoid = String::new(); // we need to read 1 lines of addional input after GET command, OID io::stdin().read_line(&mut getoid).expect("Failed to read line"); let oid: Oid = match getoid.trim().to_string().parse() { Ok(v) => v, Err(_e) => { let mystr = format!("unable to parse {} into Oid", getoid.trim()); error!("{}", mystr); return Err(mystr.into()); } }; SnmpCommands::Get(oid) } "getnext" => { let mut getoid = String::new(); // we need to read 1 lines of addional input after GETNEXT command, OID io::stdin().read_line(&mut getoid).expect("Failed to read line"); let oid: Oid = match getoid.trim().to_string().parse() { Ok(v) => v, Err(_e) => { let mystr = format!("unable to parse {} into Oid", getoid.trim()); error!("{}", mystr); return Err(mystr.into()); } }; SnmpCommands::GetNext(oid) } _ => { let mystr = format!("unable to parse {} into SnmpCommand", cmd); error!("{}", mystr); return Err(mystr.into()); } }; debug!("parsed snmp command: {}", pc); Ok(pc) } } #[cfg(test)] mod tests { use super::*; #[test] fn oid_sort1() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let oid2: Oid = "1.2.3.1".to_string().parse().unwrap(); assert!(oid1 < oid2); } #[test] fn oid_sort2() { let oid1: Oid = "1.2.3.1".to_string().parse().unwrap(); let oid2: Oid = "1.2.4".to_string().parse().unwrap(); assert!(oid1 < oid2); } #[test] fn oid_sort3() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let oid2: Oid = "1.2.4".to_string().parse().unwrap(); assert!(oid1 < oid2); } #[test] fn oid_sort4() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let oid2: Oid = "1.3.1".to_string().parse().unwrap(); assert!(oid1 < oid2); } #[test] fn oid_add_prefix1() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let oid2: Oid = "4.5".to_string().parse().unwrap(); let oid3 = oid2.add_prefix(&oid1); assert_eq!(oid3, Oid(vec![1, 2, 3, 4, 5])); } #[test] fn oid_stip_prefix1() { let oid1: Oid = "1.2.3.4.5".to_string().parse().unwrap(); let oid2: Oid = "1.2".to_string().parse().unwrap(); let oid3 = oid1.strip_prefix(&oid2).unwrap(); assert_eq!(oid3, Oid(vec![3, 4, 5])); } #[test] fn oid_stip_prefix_missmatch() { let oid1: Oid = "1.2.3.4.5".to_string().parse().unwrap(); let oid2: Oid = "1.3".to_string().parse().unwrap(); let e1 = oid1.strip_prefix(&oid2).unwrap_err(); assert_eq!(e1, OidError::PrefixMissmatch) } #[test] fn oid_stip_prefix_too_long() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let oid2: Oid = "1.2.3".to_string().parse().unwrap(); let e1 = oid1.strip_prefix(&oid2).unwrap_err(); assert_eq!(e1, OidError::PrefixTooLong) } #[test] fn oid_diplay_trait() { let oid1: Oid = "1.2.3".to_string().parse().unwrap(); let s = format!("{}", oid1); assert_eq!(s, ".1.2.3".to_string()); } }