net-mumu 0.2.0-rc.3

Network tools plugin for the Lava language
Documentation
// src/lldp/options.rs
#![allow(dead_code)]

//
// Public options for the LLDP/CDP engine.
// These are constructed by the plugin bridge (src/bridge.rs) and consumed by
// the LLDP module (engine/capture/parse/table).  Keep field names and basic
// semantics stable so the bridge doesn’t need to change when we add features.

use super::proto::DiscoveryProtocol;

/// Operating mode for the LLDP facility.
///
/// * `Listen`    — passive capture/parse of LLDP/CDP frames on one-or-more ifaces
/// * `Advertise` — actively send LLDP frames (not implemented in the engine yet)
/// * `Discover`  — combined mode (listen + advertise). For now treated like Listen.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LldpMode {
    Listen,
    Advertise,
    Discover,
}

impl Default for LldpMode {
    fn default() -> Self {
        LldpMode::Listen
    }
}

/// Options controlling capture, parsing and (eventually) advertisement.
///
/// Notes:
/// - `iface` is a convenience single-interface hint.  If provided, it is
///   ensured to be present in `ifaces`.
/// - `protocols` controls which protocols are accepted/parsed. If empty,
///   the engine will default to `[LLDP]`.
/// - We *do not* silently fall back to stubs when capture fails. The engine
///   construction returns an error that is surfaced by the iterator.
/// - Advanced knobs (`snaplen`, `promisc`, `capture_timeout_ms`, etc.) have
///   safe defaults and are optional to set from the bridge.
#[derive(Clone, Debug)]
pub struct LldpOptions {
    /// Preferred single interface (e.g. "eth0"). If set, it will be included
    /// in `ifaces` automatically by `normalized()`.
    pub iface: Option<String>,

    /// Set of interfaces to use for capture. Empty means "derive from `iface`".
    pub ifaces: Vec<String>,

    /// Operating mode.
    pub mode: LldpMode,

    /// Which discovery protocols to accept/parse.
    pub protocols: Vec<DiscoveryProtocol>,

    /// TTL to report/attach where applicable (seconds).
    pub ttl: u16,

    /// Optional limit: maximum number of rows to emit before ending the stream.
    /// `None` => unbounded (engine keeps running until the Flow graph stops it).
    pub count: Option<usize>,

    /// Optional system/hostname override for future advertisement.
    pub hostname: Option<String>,

    /// Optional port-id override for future advertisement.
    pub port_id: Option<String>,

    /// Keep for API compatibility with earlier branches. Engine ignores this;
    /// there is no stub fallback. If capture fails, the iterator yields an error.
    pub stub: bool,

    /// Verbose logging to stderr.
    pub verbose: bool,

    /* ───── Advanced capture/engine knobs (safe defaults) ───── */

    /// Maximum number of bytes captured per frame (classic MTU-friendly default).
    pub snaplen: u32,

    /// Request promiscuous mode on the capture socket (when supported).
    pub promisc: bool,

    /// Poll/recv timeout for capture (milliseconds).
    pub capture_timeout_ms: u32,

    /// Bounded channel capacity between capture → parser → table.
    pub channel_capacity: usize,

    /// Optional transmit interval for advertisement mode (milliseconds).
    /// Currently unused (advertisement not implemented).
    pub tx_interval_ms: Option<u64>,
}

impl Default for LldpOptions {
    fn default() -> Self {
        Self {
            iface: None,
            ifaces: Vec::new(),
            mode: LldpMode::Listen,
            protocols: Vec::new(),
            ttl: 120,
            count: None,
            hostname: None,
            port_id: None,
            stub: true,
            verbose: false,

            snaplen: 1518,
            promisc: true,
            capture_timeout_ms: 100,
            channel_capacity: 1024,
            tx_interval_ms: None,
        }
    }
}

impl LldpOptions {
    /// Return a copy with consistent, engine-friendly defaults:
    ///  - ensure `iface` (if present) is included in `ifaces`
    ///  - default `ifaces` from `iface` if it was empty
    ///  - default `protocols` to `[LLDP]` if empty
    pub fn normalized(mut self) -> Self {
        // Ensure iface is part of ifaces
        if let Some(ref name) = self.iface {
            if !self.ifaces.iter().any(|n| n == name) {
                self.ifaces.push(name.clone());
            }
        }

        // If still no interfaces, pick a common default
        if self.ifaces.is_empty() {
            if let Some(ref name) = self.iface {
                self.ifaces.push(name.clone());
            } else {
                // Do not guess system interfaces here. The engine will error if
                // there is truly nothing to open; this keeps behavior explicit.
            }
        }

        // Default protocols
        if self.protocols.is_empty() {
            self.protocols.push(DiscoveryProtocol::LLDP);
        }

        self
    }

    /// True when we should capture/listen for frames (Listen/Discover).
    pub fn should_listen(&self) -> bool {
        matches!(self.mode, LldpMode::Listen | LldpMode::Discover)
    }

    /// True when we should advertise frames (Advertise/Discover).
    /// (Not implemented yet by the engine.)
    pub fn should_advertise(&self) -> bool {
        matches!(self.mode, LldpMode::Advertise | LldpMode::Discover)
    }

    /// Effective interface list after normalization (convenience helper).
    pub fn effective_ifaces(&self) -> Vec<String> {
        let mut out = self.ifaces.clone();
        if let Some(ref n) = self.iface {
            if !out.iter().any(|x| x == n) {
                out.push(n.clone());
            }
        }
        out
    }
}