Files
rsnmpagent/src/rsnmplib/snmp.rs
2026-01-09 09:54:52 +01:00

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());
}
}