297 lines
8.7 KiB
Rust
297 lines
8.7 KiB
Rust
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<u32>);
|
|
|
|
impl FromStr for Oid {
|
|
type Err = ParseIntError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let r = s
|
|
.split('.')
|
|
.filter(|&s| !s.is_empty())
|
|
.map(|s| s.parse::<u32>())
|
|
.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::<Vec<String>>().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<Oid, OidError> {
|
|
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<Oid, SnmpData>,
|
|
}
|
|
|
|
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<SnmpCommands, Box<dyn Error>> {
|
|
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());
|
|
}
|
|
}
|