snmp_rust_agent 0.4.0

A framework for building SNMP v3 Agents
Documentation
mod gen_stub;
mod importer;
mod parser;
mod resolver;

use argh::FromArgs;
use log::{error, info, warn};
use std::collections::{HashMap, HashSet};
use std::env;
use std::error::Error;

#[derive(FromArgs)]
/// Generate stubs for use with snmp-rust-agent
struct Cli {
    /// output directory
    #[argh(
        option,
        short = 'o',
        default = "String::from(\"../snmp-rust/src/stubs/\")"
    )]
    out_dir: String,

    /// MIB Search path elements
    #[argh(
        option,
        short = 'p',
        default = "importer::MIB_SEARCH_PATH.iter().map(|s|s.to_string()).collect()"
    )]
    path: Vec<String>,

    /// include deprecated
    #[argh(switch, short = 'd')]
    deprecated: bool,

    /// include obsolete
    #[argh(switch)]
    obsolete: bool,

    /// names of MIBs to process
    #[argh(positional)]
    mib_names: Vec<String>,
}

impl Cli {
    fn check_status(&self, status: &str) -> bool {
        let status = status.trim();
        if self.deprecated && status == "deprecated" {
            return true;
        }
        if self.obsolete && status == "obsolete" {
            return true;
        }
        status == "current"
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    // Set sane default if environment variable does not exist
    if env::var("RUST_LOG").is_err() {
        println!("RUST_LOG environment variable not set, defaulting to warn");
        env::set_var("RUST_LOG", "warn");
    }
    env_logger::init();
    let cli: Cli = argh::from_env();
    let mut total = 0;
    let mut success = 0;
    let builtins: HashSet<&str> = vec![
        "TimeTicks",
        "OBJECT-TYPE",
        "Counter32",
        "Gauge32",
        "NOTIFICATION-TYPE",
        "Unsigned32",
        "Counter64",
        "mib-2",
        "transmission",
        "IpAddress",
        "OBJECT-IDENTITY",
        "internet",
        "Counter",
        "snmpModules",
        "snmpProxys",
        "snmpDomains",
        "Integer32",
        "MODULE-IDENTITY",
        "TEXTUAL-CONVENTION",
        "NOTIFICATION-GROUP",
        "OBJECT-GROUP",
        "MODULE-COMPLIANCE",
        "DisplayString",
        "EntryStatus",
        "OwnerString",
    ]
    .into_iter()
    .collect();

    /*let mib_path = Path::new("/var/lib/mibs/ietf/");

    for entry in fs::read_dir(mib_path)? {
       let entry = entry?;
       let path = entry.path();
       //if !path.ends_with("UDPF-MIB") {continue;}
       let text = //fs::read_to_string(&path).unwrap(); */

    let out_dir = cli.out_dir.to_string();
    let mut stub_ok = vec![];
    for argument in &cli.mib_names.clone() {
        let text_opt = importer::find_mib_text(argument, &cli.path);
        if text_opt.is_none() {
            warn!("Not found MIB {argument}, skipping, will try rest");
            continue;
        }
        let text = text_opt.unwrap();
        let mib_name = argument;
        let mut res = resolver::Resolver::new();
        let mut nodes = vec![];
        total += 1;
        let (good_parse, site) = parser::parse_mib(&text, &mut nodes);

        if good_parse {
            let imp_nodes: Vec<&parser::MibNode> = nodes
                .iter()
                .filter(|node| matches!(**node, parser::MibNode::Imp(_)))
                .collect();
            if imp_nodes.len() != 1 {
                warn!("Expected one import block");
                continue;
            }
            let imp_node = imp_nodes[0];
            let val = match imp_node {
                parser::MibNode::Imp(istruct) => istruct.imp_list.clone(),
                _ => {
                    panic!("Weird! expected ImpNode here");
                }
            };
            for (names, mib_name) in val {
                let miss: Vec<String> = names
                    .iter()
                    .filter(|x| !builtins.contains(*x))
                    .map(|x| x.to_string())
                    .collect();
                if !miss.is_empty() {
                    let txt_opt = importer::find_mib_text(mib_name, &cli.path);
                    if let Some(iraw) = txt_opt {
                        let extra = importer::process_one(&iraw, miss, mib_name, &mut res);
                        nodes.extend(extra);
                    } else {
                        error!("Import failed for {mib_name}, file not found");
                    }
                }
            }

            let mut good_parent = false;
            let inodes = nodes.clone();
            for pass in 0..3 {
                for node in &inodes {
                    match node {
                        parser::MibNode::ModId(o) => {
                            let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, parent, &v.num);
                            good_parent = added;
                            if !added && pass > 2 {
                                error!("Unknown module parent {parent} {mib_name}")
                            }
                        }
                        parser::MibNode::ObTy(o) => {
                            let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, parent, &v.num);
                            if good_parent && !added && pass > 1 {
                                error!("Unknown type parent {0} {1} {2}", o.name, parent, mib_name);
                            }
                        }
                        parser::MibNode::ObIdy(o) => {
                            let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, v.parent, &v.num);
                            if good_parent && !added && pass > 1 {
                                error!("Unknown identity parent {parent}")
                            }
                        }
                        parser::MibNode::ObIdf(o) => {
                            let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, v.parent, &v.num);
                            if good_parent && !added && pass > 1 {
                                error!("Unknown identifier parent {parent}")
                            }
                        }
                        parser::MibNode::ModCp(o) => {
                            let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, v.parent, &v.num);
                            if good_parent && !added && pass > 1 {
                                error!("Unknown compliance parent {parent}")
                            }
                        }
                        parser::MibNode::NtTy(o) => {
                            /*let v = &o.val;
                            let parent = v.parent;
                            let added = res.try_add(o.name, v.parent, &v.num);
                            if good_parent && !added && pass > 1 {
                                error!("Unknown compliance parent {parent}")
                            }*/
                            if pass > 1 {
                                for obj in &o.objects {
                                    if !res.check_name(obj) {
                                        warn!(
                                            "Unresolved object name {0} in NOTIFICATION-TYPE {1}",
                                            obj, o.name
                                        );
                                    }
                                }
                            }
                        }
                        _ => (),
                    }
                }
            }
            let mut object_types: HashMap<&str, parser::ObjectType> = HashMap::new();
            let mut tcs: HashMap<&str, parser::TextConvention> = HashMap::new();
            let mut entries: HashMap<&str, parser::Entry<'_>> = HashMap::new();
            let mut object_ids: Vec<parser::ObjectIdentity<'_>> = vec![];
            let mut mod_comps: Vec<parser::ModuleCompliance<'_>> = vec![];
            let gnodes = nodes.clone();
            for node in gnodes {
                match node {
                    parser::MibNode::ObTy(x) => {
                        if cli.check_status(x.status) {
                            object_types.insert(x.name, x);
                        }
                    }
                    parser::MibNode::Tc(x) => {
                        if cli.check_status(x.status) {
                            tcs.insert(x.name, x);
                        }
                    }
                    parser::MibNode::Ent(x) => {
                        entries.insert(x.name, x);
                    }
                    parser::MibNode::ObIdy(x) => {
                        if cli.check_status(x.status) {
                            object_ids.push(x)
                        }
                    }
                    parser::MibNode::ModCp(x) => mod_comps.push(x),
                    _ => {}
                }
            }
            // Now mark the table columns, so we don't generate scalar code for them
            for entry in entries.values() {
                for ent in entry.syntax.clone() {
                    let col_obj_res = object_types.get_mut(ent.0);
                    if let Some(c) = col_obj_res {
                        c.col = true
                    }
                }
            }
            let out_res = gen_stub::open_output(mib_name, &out_dir);
            if let Ok(out) = out_res {
                let compile_res = gen_stub::gen_stub(
                    &object_types,
                    res,
                    &tcs,
                    &entries,
                    &object_ids,
                    &mod_comps,
                    out,
                );
                if compile_res.is_ok() {
                    stub_ok.push(argument.clone());
                    success += 1;
                }
            } else {
                warn!("Output file open failed");
            }
        } else {
            error!("{mib_name}  {site}");
        }
    }
    info!("{success} read out of {total}");
    if gen_stub::loader(stub_ok).is_ok() {
        info!("Wrote stub loader");
    }
    Ok(())
}