From d11c4c3e89dc6a3b71de9e93997cd84972d8761b Mon Sep 17 00:00:00 2001 From: David Boucher Date: Mon, 14 Apr 2025 10:43:32 +0200 Subject: [PATCH] wip(generic-snmp): new lexer --- experimental/src/compute/lexer.rs | 176 ++++++++ experimental/src/compute/mod.rs | 91 ++++ experimental/src/generic/mod.rs | 297 +------------ experimental/src/grammar.lalrpop | 25 +- experimental/src/main.rs | 67 --- experimental/src/snmp/mod.rs | 666 ++++++++++++++++-------------- 6 files changed, 659 insertions(+), 663 deletions(-) create mode 100644 experimental/src/compute/lexer.rs diff --git a/experimental/src/compute/lexer.rs b/experimental/src/compute/lexer.rs new file mode 100644 index 000000000..b5286d3ab --- /dev/null +++ b/experimental/src/compute/lexer.rs @@ -0,0 +1,176 @@ +use log::debug; +use std::str; + +pub type Spanned = Result<(Loc, Tok, Loc), Error>; + +#[derive(Debug, PartialEq)] +pub enum Tok { + Num, + Id, + OpStar, + OpSlash, + OpPlus, + OpMinus, + LParen, + RParen, +} + +#[derive(Debug, PartialEq)] +pub enum LexicalError { + NotPossible, + // Not possible +} + +#[derive(Debug)] +pub struct Lexer<'input> { + chars: &'input str, + offset: usize, +} + +impl<'input> Lexer<'input> { + pub fn new(input: &'input str) -> Self { + Lexer { + chars: input, + offset: 0, + } + } + + fn number(&mut self, start: usize, chars: &[u8]) -> Option> { + // Consume digits and decimal points + let mut end = start; + let mut done = false; + for c in chars[(start + 1)..].iter() { + end += 1; + if !(*c).is_ascii_digit() && *c != b'.' { + done = true; + break; + } + } + if !done { + end = chars.len(); + } + + debug!( + "Token Number from {} to {} with value '{}'", + start, + end, + str::from_utf8(&chars[start..end]).unwrap_or("Bad value") + ); + self.offset = end; + Some(Ok((start, Tok::Num, end))) + } + + fn identifier( + &mut self, + start: usize, + chars: &[u8], + ) -> Option> { + // Consume identifier + let mut end = start; + let mut done = false; + for c in chars[(start + 1)..].iter() { + end += 1; + if !c.is_ascii_alphanumeric() && *c != b'_' { + done = true; + break; + } + } + if !done { + end = chars.len(); + } + debug!( + "Token Identifier from {} to {} with value '{}'", + start, + end, + str::from_utf8(&chars[start..end]).unwrap_or("Bad value") + ); + self.offset = end; + Some(Ok((start, Tok::Id, end))) + } +} + +impl<'input> Iterator for Lexer<'input> { + type Item = Spanned; + + fn next(&mut self) -> Option { + for (i, c) in self.chars.as_bytes().iter().enumerate().skip(self.offset) { + match *c { + b' ' | b'\t' => continue, + b'*' => { + self.offset = i + 1; + return Some(Ok((i, Tok::OpStar, i + 1))); + } + b'/' => { + self.offset = i + 1; + return Some(Ok((i, Tok::OpSlash, i + 1))); + } + b'+' => { + self.offset = i + 1; + return Some(Ok((i, Tok::OpPlus, i + 1))); + } + b'-' => { + self.offset = i + 1; + return Some(Ok((i, Tok::OpMinus, i + 1))); + } + b'(' => { + self.offset = i + 1; + return Some(Ok((i, Tok::LParen, i + 1))); + } + b')' => { + self.offset = i + 1; + return Some(Ok((i, Tok::RParen, i + 1))); + } + b'0'..=b'9' => { + // Consume digits and decimal points + return self.number(i, &self.chars.as_bytes()); + } + b'a'..=b'z' | b'A'..=b'Z' | b'_' => { + return self.identifier(i, &self.chars.as_bytes()); + } + _ => { + // Unknown character + debug!("Unknown character at {}: '{}'", i, *c as char); + self.offset = i + 1; + return Some(Err(LexicalError::NotPossible)); + } + } + } + // No more characters to process + return None; + } +} + +mod Test { + use super::*; + + #[test] + fn test_lexer_num_id_num() { + let input = "123 abc 456"; + let mut lexer = Lexer::new(input); + assert_eq!(lexer.next(), Some(Ok((0, Tok::Num, 3)))); + assert_eq!(lexer.next(), Some(Ok((4, Tok::Id, 7)))); + assert_eq!(lexer.next(), Some(Ok((8, Tok::Num, 11)))); + assert_eq!(lexer.next(), None); + } + + #[test] + fn test_lexer_id_num_id() { + let input = "abc 123 def"; + let mut lexer = Lexer::new(input); + assert_eq!(lexer.next(), Some(Ok((0, Tok::Id, 3)))); + assert_eq!(lexer.next(), Some(Ok((4, Tok::Num, 7)))); + assert_eq!(lexer.next(), Some(Ok((8, Tok::Id, 11)))); + } + + #[test] + fn test_lexer_num_op() { + let input = "1+2*3"; + let mut lexer = Lexer::new(input); + assert_eq!(lexer.next(), Some(Ok((0, Tok::Num, 1)))); + assert_eq!(lexer.next(), Some(Ok((1, Tok::OpPlus, 2)))); + assert_eq!(lexer.next(), Some(Ok((2, Tok::Num, 3)))); + assert_eq!(lexer.next(), Some(Ok((3, Tok::OpStar, 4)))); + assert_eq!(lexer.next(), Some(Ok((4, Tok::Num, 5)))); + assert_eq!(lexer.next(), None); + } +} diff --git a/experimental/src/compute/mod.rs b/experimental/src/compute/mod.rs index 80b42909f..2cf6e595f 100644 --- a/experimental/src/compute/mod.rs +++ b/experimental/src/compute/mod.rs @@ -1,4 +1,10 @@ +mod lexer; + +use lalrpop_util::lalrpop_mod; use serde::Deserialize; +use snmp::SnmpResult; + +lalrpop_mod!(grammar); #[derive(Deserialize, Debug)] pub struct Metric { @@ -15,3 +21,88 @@ pub struct Compute { pub metrics: Vec, pub aggregations: Option>, } + +pub struct Parser<'a> { + collect: &'a Vec, + parser: grammar::ExprParser, +} + +impl<'a> Parser<'a> { + pub fn new(collect: &'a Vec) -> Parser<'a> { + Parser { + collect, + parser: grammar::ExprParser::new(), + } + } + + pub fn eval(&self, expr: &str) -> f32 { + 2.0_f32 + } +} + +mod Test { + use super::*; + + #[test] + fn term() { + assert!(grammar::ExprParser::new().parse("132").is_ok()); + assert!(grammar::ExprParser::new().parse("((132))").is_ok()); + assert!(grammar::ExprParser::new().parse("((132)))").is_err()); + } + + #[test] + fn sum() { + let res = grammar::ExprParser::new().parse("1 + 2"); + assert!(res.is_ok()); + assert!(res.unwrap() == 3_f32); + let res = grammar::ExprParser::new().parse("1 + 2 + 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 6_f32); + let res = grammar::ExprParser::new().parse("1 - 2 + 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 2_f32); + let res = grammar::ExprParser::new().parse("1 + 2 - 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 0_f32); + } + + #[test] + fn product() { + let res = grammar::ExprParser::new().parse("2 * 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 6_f32); + + let res = grammar::ExprParser::new().parse("2 * 3 * 4"); + assert!(res.is_ok()); + assert!(res.unwrap() == 24_f32); + + let res = grammar::ExprParser::new().parse("2 * 3 / 2"); + assert!(res.is_ok()); + assert!(res.unwrap() == 3_f32); + + // let res = grammar::ProductParser::new().parse("2 / 0"); + // assert!(res.is_err()); + } + + #[test] + fn sum_product() { + let res = grammar::ExprParser::new().parse("1 + 2 * 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 7_f32); + + let res = grammar::ExprParser::new().parse("1 + (3 + 2 * 3) / 3"); + assert!(res.is_ok()); + assert!(res.unwrap() == 4_f32); + } + + #[test] + fn function() { + let res = grammar::ExprParser::new().parse("Average(1, 2, 3)"); + assert!(res.is_ok()); + assert!(res.unwrap() == 2_f32); + + let res = grammar::ExprParser::new().parse("Average(1 + 2 * 2, 3, 4)"); + assert!(res.is_ok()); + assert!(res.unwrap() == 4_f32); + } +} diff --git a/experimental/src/generic/mod.rs b/experimental/src/generic/mod.rs index e890b4a14..d6f5b782a 100644 --- a/experimental/src/generic/mod.rs +++ b/experimental/src/generic/mod.rs @@ -1,10 +1,9 @@ extern crate serde; extern crate serde_json; -use compute::Compute; +use compute::{Compute, Parser}; use serde::Deserialize; -use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels, SnmpResult, SnmpValue}; - +use snmp::{snmp_bulk_get, snmp_bulk_walk, snmp_bulk_walk_with_labels}; use std::collections::{BTreeMap, HashMap}; #[derive(Copy, Clone, PartialEq)] @@ -99,67 +98,6 @@ fn compute_status(value: f32, warn: &Option, crit: &Option) -> S Status::Ok } -//fn build_metrics<'a>( -// values: &Vec<(String, f32)>, -// ag: &Option<(&str, usize, f32)>, -// ext: &'a CommandExt, -//) -> (Vec>, Status) { -// let mut metrics: Vec = Vec::new(); -// let mut status = Status::Ok; -// match ag { -// Some(a) => { -// // The agregation is located in first place -// if a.1 == 0 { -// let w = &ext.warning_agregation; -// let c = &ext.critical_agregation; -// let current_status = -// compute_status(a.2, &ext.warning_agregation, &ext.critical_agregation); -// metrics.push(Metric { -// name: a.0.to_string(), -// value: a.2, -// warning: w, -// critical: c, -// status: current_status, -// agregated: true, -// }); -// status = worst(current_status, status); -// } -// } -// None => (), -// } -// values.iter().enumerate().for_each(|(i, v)| { -// let current_status = compute_status(v.1, &ext.warning_core, &ext.critical_core); -// metrics.push(Metric { -// name: values[i].0.clone(), -// value: v.1, -// warning: &ext.warning_core, -// critical: &ext.critical_core, -// status: current_status, -// agregated: false, -// }); -// status = worst(current_status, status); -// }); -// match ag { -// Some(a) => { -// if a.1 > 0 { -// let current_status = -// compute_status(a.2, &ext.warning_agregation, &ext.critical_agregation); -// metrics.push(Metric { -// name: a.0.to_string(), -// value: a.2, -// warning: &ext.warning_agregation, -// critical: &ext.critical_agregation, -// status: current_status, -// agregated: true, -// }); -// status = worst(current_status, status); -// } -// } -// None => (), -// } -// (metrics, status) -//} - impl Command { pub fn execute( &self, @@ -181,239 +119,34 @@ impl Command { ); collect.push(r); } else { - let r = snmp_bulk_walk(target, version, community, &s.oid); + let r = snmp_bulk_walk(target, version, community, &s.oid, &s.name); + collect.push(r); } - // match r.variables.len() { - // 1 => { - // let result = r.variables.get(0).unwrap(); - // if let SnmpValue::Integer(v) = result.value { - // res.insert(s.name.clone(), v); - // } - // } - // _ => { - // for (idx, result) in r.variables.iter().enumerate() { - // if let SnmpValue::Integer(v) = result.value { - // res.insert(format!("{}_{}", &s.name, idx), v); - // } - // } - // } - // } } QueryType::Get => { to_get.push(s.oid.as_str()); - get_name.push(&s.name); + get_name.push(s.name.as_str()); } } } - println!("{:#?}", collect); - //let mut variables = HashMap::new(); - //for res in collect.iter_mut() { - // variables.extend(res.get_variables()); - //} - for metric in &self.compute.metrics { - if let Some(prefix) = &metric.prefix {} + let p = Parser::new(&collect); + for (idx, metric) in self.compute.metrics.iter().enumerate() { + let name = match &metric.prefix { + Some(prefix) => format!("{}#{}", p.eval(prefix), metric.name), + None => format!("{}#{}", idx, metric.name), + }; } if !to_get.is_empty() { - // let r = snmp_bulk_get(target, version, community, 1, 1, &to_get); - // for (idx, result) in r.variables.iter().enumerate() { - // if let SnmpValue::Integer(v) = result.value { - // res.insert(get_name[idx].to_string(), v); - // } - // } + let r = snmp_bulk_get(target, version, community, 1, 1, &to_get, &get_name); + collect.push(r); } - // let mut aggregation = ("", 0, Operation::None); - // let mut res: Option<(&str, SnmpResult)> = None; - // for (idx, entry) in self.leaf.entries.iter().enumerate() { - // match entry { - // Entry::Agregation(op) => { - // agregation = (&op.name, idx, op.op); - // } - // Entry::Query(query) => match query.query { - // QueryType::Walk => { - // res = Some(( - // &query.name, - // snmp_bulk_walk(target, version, community, &query.oid), - // )); - // } - // }, - // } - // } - // match res { - // Some(r) => { - // let mut values: Vec<(String, f32)> = Vec::new(); - // let mut idx = 0; - // r.1.variables.iter().for_each(|v| { - // let label = r.0.replace("{idx}", &idx.to_string()); - // values.push((label, v.value.parse().unwrap())); - // idx += 1; - // }); - // let count = values.len(); - // let ag = match agregation.2 { - // Operation::Average => { - // let sum: f32 = values.iter().map(|(_, v)| v).sum(); - // let avg = sum / values.len() as f32; - // Some((agregation.0, agregation.1, avg)) - // } - // _ => None, - // }; - // let (metrics, status) = build_metrics(&values, &ag, &ext); - // let output = self.build_output(count, status, &metrics, &ag, &ext); - // return CmdResult { status, output }; - // } - // None => { - // return CmdResult { - // status: Status::Unknown, - // output: "No result".to_string(), - // }; - // } - // } + println!("{:#?}", collect); + CmdResult { status: Status::Unknown, output: "No result".to_string(), } } - - // fn build_output( - // &self, - // count: usize, - // status: Status, - // metrics: &Vec, - // ag: &Option<(&str, usize, f32)>, - // ext: &CommandExt, - // ) -> String { - // let no_threshold = ext.warning_core.is_none() - // && ext.critical_core.is_none() - // && ext.warning_agregation.is_none() - // && ext.critical_agregation.is_none(); - // let write_details = - // no_threshold || (ext.warning_core.is_some() || ext.critical_core.is_some()); - // let write_agregation_details = - // no_threshold || (ext.warning_agregation.is_some() || ext.critical_agregation.is_some()); - // let mut output_text = "".to_string(); - // let mut begun = false; - // if &self.leaf.output.header != "" { - // output_text = self.leaf.output.header.replace("{status}", status.as_str()); - // } - // for line in &self.leaf.output.text { - // if line.contains("idx") { - // if write_details { - // // We have to iterate on metrics - // let mut output_vec = (Vec::new(), Vec::new(), Vec::new()); - // let mut idx = 0; - // for m in metrics.iter() { - // if !m.agregated { - // let text = line - // .replace("{idx}", idx.to_string().as_str()) - // .replace("{name}", m.name.as_str()) - // .replace("{value}", format!("{:.2}", m.value).as_str()) - // .replace("{count}", count.to_string().as_str()); - // match m.status { - // Status::Ok => { - // output_vec.0.push(text); - // } - // Status::Warning => { - // output_vec.1.push(text); - // } - // Status::Critical => { - // output_vec.2.push(text); - // } - // Status::Unknown => (), - // } - // idx += 1; - // } - // } - // if !output_vec.2.is_empty() { - // if begun { - // output_text += " - "; - // } else { - // begun = true; - // } - // output_text += output_vec.2.join(" - ").as_str(); - // } - // if !output_vec.1.is_empty() { - // if begun { - // output_text += " - "; - // } else { - // begun = true; - // } - // output_text += output_vec.1.join(" - ").as_str(); - // } - // if !output_vec.0.is_empty() { - // if begun { - // output_text += " - "; - // } - // output_text += output_vec.0.join(" - ").as_str(); - // } - // } - // } else { - // if write_agregation_details { - // match ag { - // Some(a) => { - // output_text += line - // .replace( - // format!("{{{}}}", a.0).as_str(), - // format!("{:.2}", a.2).as_str(), - // ) - // .replace("{count}", count.to_string().as_str()) - // .as_str(); - // begun = true; - // } - // None => output_text += line, - // }; - // } - // } - // } - // - // let mut perfdata = " |".to_string(); - // match &self.leaf.data { - // Some(d) => { - // metrics.iter().for_each(|m| { - // perfdata += format!( - // " '{}'={}{};{};{};{};{}", - // m.name, - // m.value, - // d.uom, - // match m.warning { - // Some(m) => m.to_string(), - // None => "".to_string(), - // }, - // match m.critical { - // Some(m) => m.to_string(), - // None => "".to_string(), - // }, - // match d.min { - // Some(m) => m.to_string(), - // None => "".to_string(), - // }, - // match d.max { - // Some(m) => m.to_string(), - // None => "".to_string(), - // }, - // ) - // .as_str(); - // }); - // } - // None => { - // metrics.iter().for_each(|m| { - // perfdata += format!( - // " '{}'={};{};{}", - // m.name, - // m.value, - // match m.warning { - // Some(v) => v.to_string(), - // None => "".to_string(), - // }, - // match m.critical { - // Some(v) => v.to_string(), - // None => "".to_string(), - // } - // ) - // .as_str(); - // }); - // } - // }; - // output_text + &perfdata - // } } diff --git a/experimental/src/grammar.lalrpop b/experimental/src/grammar.lalrpop index cc82b420a..bce660365 100644 --- a/experimental/src/grammar.lalrpop +++ b/experimental/src/grammar.lalrpop @@ -2,19 +2,19 @@ use std::str::FromStr; grammar; -pub Sum: f32 = { +Sum: f32 = { "+" => s + p, "-" => s - p, => p, }; -pub Product: f32 = { - "*" => p * t, - "/" => p / t, - => t, +Product: f32 = { + "*" => p * t, + "/" => p / t, + => t, }; -pub Args = Comma; // (0) +Args = Comma; // (0) Comma: Vec = { // (1) ",")*> => match e { // (2) @@ -26,10 +26,19 @@ Comma: Vec = { // (1) } }; -pub Term: f32 = { +pub Expr: f32 = { => n, "(" ")" => t, "Average" "(" ")" => { let sum:f32 = a.iter().sum(); sum / a.len() as f32 } }; -Num: f32 = => f32::from_str(s).unwrap(); +match { + r"-?[0-9.]+" => NUM, +} else { + r"\w+" => ID, + _ +} + +Num: f32 = => f32::from_str(s).unwrap(); + +Identifier: String = => s.to_string(); diff --git a/experimental/src/main.rs b/experimental/src/main.rs index 1adce216e..ebc8ea290 100644 --- a/experimental/src/main.rs +++ b/experimental/src/main.rs @@ -135,70 +135,3 @@ fn main() { //println!("{}", result.output); //std::process::exit(result.status as i32); } - -mod Test { - use super::*; - - #[test] - fn term() { - assert!(grammar::TermParser::new().parse("132").is_ok()); - assert!(grammar::TermParser::new().parse("((132))").is_ok()); - assert!(grammar::TermParser::new().parse("((132)))").is_err()); - } - - #[test] - fn sum() { - let res = grammar::SumParser::new().parse("1 + 2"); - assert!(res.is_ok()); - assert!(res.unwrap() == 3_f32); - let res = grammar::SumParser::new().parse("1 + 2 + 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 6_f32); - let res = grammar::SumParser::new().parse("1 - 2 + 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 2_f32); - let res = grammar::SumParser::new().parse("1 + 2 - 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 0_f32); - } - - #[test] - fn product() { - let res = grammar::ProductParser::new().parse("2 * 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 6_f32); - - let res = grammar::ProductParser::new().parse("2 * 3 * 4"); - assert!(res.is_ok()); - assert!(res.unwrap() == 24_f32); - - let res = grammar::ProductParser::new().parse("2 * 3 / 2"); - assert!(res.is_ok()); - assert!(res.unwrap() == 3_f32); - - // let res = grammar::ProductParser::new().parse("2 / 0"); - // assert!(res.is_err()); - } - - #[test] - fn sum_product() { - let res = grammar::SumParser::new().parse("1 + 2 * 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 7_f32); - - let res = grammar::SumParser::new().parse("1 + (3 + 2 * 3) / 3"); - assert!(res.is_ok()); - assert!(res.unwrap() == 4_f32); - } - - #[test] - fn function() { - let res = grammar::TermParser::new().parse("Average(1, 2, 3)"); - assert!(res.is_ok()); - assert!(res.unwrap() == 2_f32); - - let res = grammar::TermParser::new().parse("Average(1 + 2 * 2, 3, 4)"); - assert!(res.is_ok()); - assert!(res.unwrap() == 4_f32); - } -} diff --git a/experimental/src/snmp/mod.rs b/experimental/src/snmp/mod.rs index c2b5792eb..df54abd45 100644 --- a/experimental/src/snmp/mod.rs +++ b/experimental/src/snmp/mod.rs @@ -5,13 +5,13 @@ extern crate rasn_snmp; use log::{info, trace, warn}; use rasn::types::ObjectIdentifier; +use rasn_snmp::v2::BulkPdu; use rasn_snmp::v2::Pdus; use rasn_snmp::v2::VarBind; use rasn_snmp::v2::VarBindValue; -use rasn_snmp::v2::{BulkPdu, Pdu}; use rasn_snmp::v2::{GetBulkRequest, GetNextRequest, GetRequest}; use rasn_snmp::v2c::Message; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::convert::TryInto; use std::net::UdpSocket; @@ -34,105 +34,6 @@ pub struct SnmpResult { last_oid: Vec, } -//#[derive(Debug, PartialEq)] -//pub struct SnmpSubResult<'a> { -// pub label: &'a str, -// pub value: &'a str, -// pub variables: Vec, -//} - -//#[derive(Debug, PartialEq)] -//pub struct SnmpResult<'a> { -// pub labels: Vec>, -// last_oid: Vec, -//} - -#[derive(Debug, PartialEq)] -pub enum SnmpValue { - Integer(i64), - String(String), -} - -#[derive(Debug, PartialEq)] -pub struct SnmpVariable { - pub name: String, - pub value: SnmpValue, -} - -//impl<'a> SnmpResult<'a> { -// fn new(labels: &'a BTreeMap) -> SnmpResult<'a> { -// let mut retval: Vec = Vec::new(); -// retval.reserve(labels.len()); -// for (label, value) in labels.iter() { -// let item = SnmpSubResult { -// label: &label, -// value: &value, -// variables: Vec::new(), -// }; -// retval.push(item); -// } -// SnmpResult { -// labels: retval, -// last_oid: Vec::new(), -// } -// } -// -// fn new_simple() -> SnmpResult<'a> { -// let label = SnmpSubResult { -// label: "", -// value: "", -// variables: Vec::new(), -// }; -// SnmpResult { -// labels: vec![label], -// last_oid: Vec::new(), -// } -// } -// -// fn add_variable(&mut self, idx: usize, name: String, value: SnmpValue) { -// self.labels[idx] -// .variables -// .push(SnmpVariable { name, value }); -// } -// -// fn get_last_oid(&self, idx: usize) -> &str { -// if self.labels.is_empty() { -// return ""; -// } -// let last = self.labels[idx].variables.iter().last().unwrap(); -// if last.name.is_empty() { -// return ""; -// } -// last.name.as_str() -// } -// -// fn merge(&mut self, mut other: SnmpResult) { -// for v in other.labels.iter_mut() { -// for vv in self.labels.iter_mut() { -// if v.label == vv.label { -// while !v.variables.is_empty() { -// let last = v.variables.pop().unwrap(); -// vv.variables.push(last); -// } -// break; -// } -// } -// } -// self.last_oid = other.last_oid; -// } -// -// pub fn get_variables(&self) -> HashMap { -// let retval = HashMap::new(); -// // for r in self.labels.iter() { -// // for s in r.variables.iter() { -// // let name = format!("{}.{}", self.name, r.label, s.label); -// // retval.insert(name, s.value); -// // } -// // } -// retval -// } -//} - //pub fn snmp_get(target: &str, oid: &str, community: &str) -> SnmpResult { // let oid_tab = oid // .split('.') @@ -266,63 +167,63 @@ pub fn snmp_bulk_get<'a>( non_repeaters: u32, max_repetitions: u32, oid: &Vec<&str>, + names: &Vec<&str>, ) -> SnmpResult { - // let mut oids_tab = oid - // .iter() - // .map(|x| { - // x.split('.') - // .map(|x| x.parse::().unwrap()) - // .collect::>() - // }) - // .collect::>>(); - // + let mut oids_tab = oid + .iter() + .map(|x| { + x.split('.') + .map(|x| x.parse::().unwrap()) + .collect::>() + }) + .collect::>>(); + let mut retval = SnmpResult { items: BTreeMap::new(), last_oid: Vec::new(), }; - // let mut request_id: i32 = 1; - // - // let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - // socket.connect(target).expect("connect function failed"); - // let duration = std::time::Duration::from_millis(1000); - // socket.set_read_timeout(Some(duration)).unwrap(); - // - // let variable_bindings = oids_tab - // .iter() - // .map(|x| VarBind { - // name: ObjectIdentifier::new_unchecked(x.to_vec().into()), - // value: VarBindValue::Unspecified, - // }) - // .collect::>(); - // - // let pdu = BulkPdu { - // request_id, - // non_repeaters, - // max_repetitions, - // variable_bindings, - // }; - // - // let get_request: GetBulkRequest = GetBulkRequest(pdu); - // - // let message: Message = Message { - // version: 1.into(), - // community: community.to_string().into(), - // data: get_request.into(), - // }; - // - // // Send the message through an UDP socket - // let encoded: Vec = rasn::der::encode(&message).unwrap(); - // let res: usize = socket.send(&encoded).unwrap(); - // assert!(res == encoded.len()); - // - // let mut buf: [u8; 1024] = [0; 1024]; - // let resp: (usize, std::net::SocketAddr) = socket.recv_from(buf.as_mut_slice()).unwrap(); - // - // trace!("Received {} bytes", resp.0); - // assert!(resp.0 > 0); - // let decoded: Message = rasn::ber::decode(&buf[0..resp.0]).unwrap(); - // let (result, _completed) = build_response(decoded, "", false); - // retval.merge(result); + let mut request_id: i32 = 1; + + let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + socket.connect(target).expect("connect function failed"); + let duration = std::time::Duration::from_millis(1000); + socket.set_read_timeout(Some(duration)).unwrap(); + + let variable_bindings = oids_tab + .iter() + .map(|x| VarBind { + name: ObjectIdentifier::new_unchecked(x.to_vec().into()), + value: VarBindValue::Unspecified, + }) + .collect::>(); + + let pdu = BulkPdu { + request_id, + non_repeaters, + max_repetitions, + variable_bindings, + }; + + let get_request: GetBulkRequest = GetBulkRequest(pdu); + + let message: Message = Message { + version: 1.into(), + community: community.to_string().into(), + data: get_request.into(), + }; + + // Send the message through an UDP socket + let encoded: Vec = rasn::der::encode(&message).unwrap(); + let res: usize = socket.send(&encoded).unwrap(); + assert!(res == encoded.len()); + + let mut buf: [u8; 1024] = [0; 1024]; + let resp: (usize, std::net::SocketAddr) = socket.recv_from(buf.as_mut_slice()).unwrap(); + + trace!("Received {} bytes", resp.0); + assert!(resp.0 > 0); + let decoded: Message = rasn::ber::decode(&buf[0..resp.0]).unwrap(); + let _completed = retval.build_response_with_names(decoded, "", names, false); retval } @@ -342,75 +243,68 @@ pub fn snmp_bulk_get<'a>( /// use snmp_rust::snmp_bulk_walk; /// let result = snmp_bulk_walk("127.0.0.1:161", "2c", "public", "1.3.6.1.2.1.25.3.3.1.2"); /// ``` -pub fn snmp_bulk_walk<'a>(target: &str, _version: &str, community: &str, oid: &str) -> SnmpResult { - let mut oid_tab = oid +pub fn snmp_bulk_walk<'a>( + target: &str, + _version: &str, + community: &str, + oid: &str, + snmp_name: &str, +) -> SnmpResult { + let oid_init = oid .split('.') .map(|x| x.parse::().unwrap()) .collect::>(); - + let mut oid_tab = &oid_init; let mut retval = SnmpResult { items: BTreeMap::new(), last_oid: Vec::new(), }; - // let mut request_id: i32 = 1; - // - // let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); - // socket.connect(target).expect("connect function failed"); - // let duration = std::time::Duration::from_millis(1000); - // socket.set_read_timeout(Some(duration)).unwrap(); - // - // loop { - // let variable_bindings = vec![VarBind { - // name: ObjectIdentifier::new_unchecked(oid_tab.to_vec().into()), - // value: VarBindValue::Unspecified, - // }]; - // - // let pdu = BulkPdu { - // request_id, - // non_repeaters: 0, - // max_repetitions: 10, - // variable_bindings, - // }; - // - // let get_request: GetBulkRequest = GetBulkRequest(pdu); - // - // let message: Message = Message { - // version: 1.into(), - // community: community.to_string().into(), - // data: get_request.into(), - // }; - // - // // Send the message through an UDP socket - // let encoded: Vec = rasn::der::encode(&message).unwrap(); - // let res: usize = socket.send(&encoded).unwrap(); - // assert!(res == encoded.len()); - // - // let mut buf: [u8; 1024] = [0; 1024]; - // let resp: (usize, std::net::SocketAddr) = socket.recv_from(buf.as_mut_slice()).unwrap(); - // - // trace!("Received {} bytes", resp.0); - // assert!(resp.0 > 0); - // let decoded: Message = rasn::ber::decode(&buf[0..resp.0]).unwrap(); - // if let Pdus::Response(resp) = &decoded.data { - // let resp_oid = &resp.0.variable_bindings[0].name; - // let n = resp_oid.len() - 1; - // } - // let (result, completed) = build_response(decoded, &oid, true); - // retval.merge(result); - // if completed { - // break; - // } - // if !retval.variables.is_empty() { - // oid_tab = retval - // .variables - // .last() - // .unwrap() - // .name - // .split('.') - // .map(|x| x.parse::().unwrap()) - // .collect::>(); - // } - // } + let request_id: i32 = 1; + + let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + socket.connect(target).expect("connect function failed"); + let duration = std::time::Duration::from_millis(1000); + socket.set_read_timeout(Some(duration)).unwrap(); + + loop { + let variable_bindings = vec![VarBind { + name: ObjectIdentifier::new_unchecked(oid_tab.to_vec().into()), + value: VarBindValue::Unspecified, + }]; + + let pdu = BulkPdu { + request_id, + non_repeaters: 0, + max_repetitions: 10, + variable_bindings, + }; + + let get_request: GetBulkRequest = GetBulkRequest(pdu); + + let message: Message = Message { + version: 1.into(), + community: community.to_string().into(), + data: get_request.into(), + }; + + // Send the message through an UDP socket + let encoded: Vec = rasn::der::encode(&message).unwrap(); + let res: usize = socket.send(&encoded).unwrap(); + assert!(res == encoded.len()); + + let mut buf: [u8; 1024] = [0; 1024]; + let resp: (usize, std::net::SocketAddr) = socket.recv_from(buf.as_mut_slice()).unwrap(); + + trace!("Received {} bytes", resp.0); + assert!(resp.0 > 0); + let decoded: Message = rasn::ber::decode(&buf[0..resp.0]).unwrap(); + let completed = retval.build_response(decoded, &oid, snmp_name, true); + + if completed { + break; + } + oid_tab = &retval.last_oid; + } retval } @@ -471,10 +365,6 @@ pub fn snmp_bulk_walk_with_labels<'a>( trace!("Received {} bytes", resp.0); assert!(resp.0 > 0); let decoded: Message = rasn::ber::decode(&buf[0..resp.0]).unwrap(); - //if let Pdus::Response(resp) = &decoded.data { - // let resp_oid = &resp.0.variable_bindings[0].name; - // let oid_tab = &resp.0.variable_bindings.last().unwrap().name; - //} let completed = retval.build_response_with_labels(decoded, &oid, snmp_name, labels, true); if completed { break; @@ -600,95 +490,259 @@ impl SnmpResult { } completed } + + fn build_response_with_names<'a>( + &mut self, + decoded: Message, + oid: &str, + names: &Vec<&str>, + walk: bool, + ) -> bool { + let mut completed = false; + + if let Pdus::Response(resp) = &decoded.data { + let vars = &resp.0.variable_bindings; + for (idx, var) in vars.iter().enumerate() { + let name = var.name.to_string(); + self.last_oid = name + .split('.') + .map(|x| x.parse::().unwrap()) + .collect::>(); + if walk { + if !name.starts_with(oid) { + completed = true; + break; + } + } + let prefix: &str = &name[..name.rfind('.').unwrap()]; + let mut typ = ValueType::None(()); + let value = match &var.value { + VarBindValue::Unspecified => { + warn!("Unspecified"); + } + VarBindValue::NoSuchObject => { + warn!("NoSuchObject"); + } + VarBindValue::NoSuchInstance => { + warn!("NoSuchInstance"); + } + VarBindValue::EndOfMibView => { + warn!("EndOfMibView"); + } + VarBindValue::Value(value) => { + warn!("Value {:?}", &value); + match value { + rasn_smi::v2::ObjectSyntax::Simple(value) => { + info!("Simple {:?}", value); + match value { + rasn_smi::v2::SimpleSyntax::Integer(value) => { + typ = ValueType::Integer(value.try_into().unwrap()); + } + rasn_smi::v2::SimpleSyntax::String(value) => { + // We transform the value into a rust String + typ = ValueType::String( + String::from_utf8(value.to_vec()).unwrap(), + ); + } + rasn_smi::v2::SimpleSyntax::ObjectId(value) => { + let oid: String = + value.iter().map(|&id| id.to_string() + ".").collect(); + typ = ValueType::String(oid); + } + _ => { + typ = ValueType::String("Other".to_string()); + } + } + } + _ => { + info!("other"); + } + } + } + }; + let key = format!("{}", names[idx]); + self.items + .entry(key) + .and_modify(|e| match e { + SnmpItem::Nbr(v) => v.push(match &typ { + ValueType::Float(f) => *f, + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(_) => { + panic!("Value should be a float"); + } + ValueType::Integer(i) => *i as f32, + }), + SnmpItem::Str(v) => v.push(match &typ { + ValueType::Float(_) => { + panic!("Value should be a string"); + } + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(s) => s.to_string(), + ValueType::Integer(_) => { + panic!("Value should be a string"); + } + }), + }) + .or_insert(match typ { + ValueType::Float(f) => SnmpItem::Nbr(vec![f]), + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(s) => SnmpItem::Str(vec![s]), + ValueType::Integer(i) => SnmpItem::Nbr(vec![i as f32]), + }); + } + } + completed + } + fn build_response<'a>( + &mut self, + decoded: Message, + oid: &str, + snmp_name: &str, + walk: bool, + ) -> bool { + let mut completed = false; + + if let Pdus::Response(resp) = &decoded.data { + let vars = &resp.0.variable_bindings; + for var in vars { + let name = var.name.to_string(); + self.last_oid = name + .split('.') + .map(|x| x.parse::().unwrap()) + .collect::>(); + if walk { + if !name.starts_with(oid) { + completed = true; + break; + } + } + let prefix: &str = &name[..name.rfind('.').unwrap()]; + let mut typ = ValueType::None(()); + let value = match &var.value { + VarBindValue::Unspecified => { + warn!("Unspecified"); + } + VarBindValue::NoSuchObject => { + warn!("NoSuchObject"); + } + VarBindValue::NoSuchInstance => { + warn!("NoSuchInstance"); + } + VarBindValue::EndOfMibView => { + warn!("EndOfMibView"); + } + VarBindValue::Value(value) => { + warn!("Value {:?}", &value); + match value { + rasn_smi::v2::ObjectSyntax::Simple(value) => { + info!("Simple {:?}", value); + match value { + rasn_smi::v2::SimpleSyntax::Integer(value) => { + typ = ValueType::Integer(value.try_into().unwrap()); + } + rasn_smi::v2::SimpleSyntax::String(value) => { + // We transform the value into a rust String + typ = ValueType::String( + String::from_utf8(value.to_vec()).unwrap(), + ); + } + rasn_smi::v2::SimpleSyntax::ObjectId(value) => { + let oid: String = + value.iter().map(|&id| id.to_string() + ".").collect(); + typ = ValueType::String(oid); + } + _ => { + typ = ValueType::String("Other".to_string()); + } + } + } + _ => { + info!("other"); + } + } + //match value { + // rasn_smi::v2::ObjectSyntax::Simple(value) => { + // info!("Simple {:?}", value); + // match value { + // rasn_smi::v2::ObjectSyntax::Simple(value) => { + // info!("Simple {:?}", value); + // match value { + // rasn_smi::v2::SimpleSyntax::Integer(value) => { + // typ = ValueType::Integer(value.try_into().unwrap()); + // } + // rasn_smi::v2::SimpleSyntax::String(value) => { + // // We transform the value into a rust String + // typ = ValueType::String( + // String::from_utf8(value.to_vec()).unwrap(), + // ); + // } + // rasn_smi::v2::SimpleSyntax::ObjectId(value) => { + // let oid: String = value + // .iter() + // .map(|&id| id.to_string() + ".") + // .collect(); + // typ = ValueType::String(oid); + // } + // _ => { + // typ = ValueType::String("Other".to_string()); + // } + // }; + // } + // rasn_smi::v2::ObjectSyntax::ApplicationWide(value) => { + // info!("Application {:?}", value); + // } + // }; + // } + //} + } + }; + let key = format!("{}", snmp_name); + self.items + .entry(key) + .and_modify(|e| match e { + SnmpItem::Nbr(v) => v.push(match &typ { + ValueType::Float(f) => *f, + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(_) => { + panic!("Value should be a float"); + } + ValueType::Integer(i) => *i as f32, + }), + SnmpItem::Str(v) => v.push(match &typ { + ValueType::Float(_) => { + panic!("Value should be a string"); + } + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(s) => s.to_string(), + ValueType::Integer(_) => { + panic!("Value should be a string"); + } + }), + }) + .or_insert(match typ { + ValueType::Float(f) => SnmpItem::Nbr(vec![f]), + ValueType::None(()) => { + panic!("Should not arrive"); + } + ValueType::String(s) => SnmpItem::Str(vec![s]), + ValueType::Integer(i) => SnmpItem::Nbr(vec![i as f32]), + }); + } + } + completed + } } -//fn build_response<'a>(decoded: Message, oid: &str, walk: bool) -> (SnmpResult, bool) { -// let mut retval = SnmpResult{items: BTreeMap::new(), last_oid: Vec::new()}; -// let mut completed = false; -// let mut last_oid: &str = ""; -// -// if let Pdus::Response(resp) = &decoded.data { -// let vars = &resp.0.variable_bindings; -// for var in vars { -// let name = var.name.to_string(); -// if walk { -// if !name.starts_with(oid) { -// completed = true; -// last_oid = ""; -// break; -// } -// } -// let mut idx: i32 = -1; -// retval.labels.iter().enumerate().for_each(|label| { -// if name.ends_with(label.1.label) { -// idx = label.0 as i32; -// return; -// } -// }); -// if idx == -1 { -// continue; -// } -// match &var.value { -// VarBindValue::Unspecified => { -// warn!("Unspecified"); -// } -// VarBindValue::NoSuchObject => { -// warn!("NoSuchObject"); -// } -// VarBindValue::NoSuchInstance => { -// warn!("NoSuchInstance"); -// } -// VarBindValue::EndOfMibView => { -// warn!("EndOfMibView"); -// } -// VarBindValue::Value(value) => { -// warn!("Value {:?}", &value); -// match value { -// rasn_smi::v2::ObjectSyntax::Simple(value) => { -// info!("Simple {:?}", value); -// match value { -// rasn_smi::v2::SimpleSyntax::Integer(value) => { -// let v = value.try_into().unwrap(); -// retval.add_variable(idx as usize, name, SnmpValue::Integer(v)); -// last_oid = retval.get_last_oid(idx as usize); -// } -// rasn_smi::v2::SimpleSyntax::String(value) => { -// // We transform the value into a rust String -// retval.add_variable( -// idx as usize, -// name, -// SnmpValue::String( -// String::from_utf8(value.to_vec()).unwrap(), -// ), -// ); -// last_oid = retval.get_last_oid(idx as usize); -// } -// rasn_smi::v2::SimpleSyntax::ObjectId(value) => { -// let oid: String = -// value.iter().map(|&id| id.to_string() + ".").collect(); -// retval.add_variable(idx as usize, name, SnmpValue::String(oid)); -// last_oid = retval.get_last_oid(idx as usize); -// } -// _ => { -// retval.add_variable( -// idx as usize, -// name, -// SnmpValue::String("Other".to_string()), -// ); -// last_oid = retval.get_last_oid(idx as usize); -// } -// }; -// } -// rasn_smi::v2::ObjectSyntax::ApplicationWide(value) => { -// info!("Application {:?}", value); -// } -// }; -// } -// }; -// } -// } -// (retval, completed) -//} //mod tests { // use super::*; //