# MIBGen 1.0 Developer User Guide

MIBGen 1.0 creates extensible, safe-Rust SNMP agents from SMIv1 and SMIv2 MIB definitions. This guide
is for software developers who understand basic SNMP concepts such as OIDs, scalar instances,
tables, GET, GET-NEXT, GET-BULK, SET, communities, and SNMPv3 security, but who have not used the
MIBGen runtime or generated project structure before.

The guide covers the desktop application on macOS, Linux, and Windows. It does not cover the
command-line generator.

## Contents

1. [What MIBGen generates](#what-mibgen-generates)
2. [Install MIBGen](#install-mibgen)
3. [Requirements](#requirements)
4. [The six-step workflow](#the-six-step-workflow)
5. [Tutorial: create an agent](#tutorial-create-an-agent)
6. [Beginner end-to-end tutorial](#beginner-end-to-end-tutorial)
7. [Understand the generated project](#understand-the-generated-project)
8. [Implement generated scalars](#implement-generated-scalars)
9. [Implement a generated table](#implement-a-generated-table)
10. [Build and run the agent](#build-and-run-the-agent)
11. [Test the agent with SNMP](#test-the-agent-with-snmp)
12. [Add another target](#add-another-target)
13. [Synchronize a revised MIB](#synchronize-a-revised-mib)
14. [Provider development rules](#provider-development-rules)
15. [MIB syntax and Rust values](#mib-syntax-and-rust-values)
16. [Agent configuration reference](#agent-configuration-reference)
17. [Troubleshooting](#troubleshooting)
18. [Production release checklist](#production-release-checklist)
19. [Glossary](#glossary)

## What MIBGen generates

A MIB describes the management schema:

- Numeric OIDs and symbolic names.
- Scalar and table structure.
- Table indexes and `AUGMENTS` relationships.
- Value syntax, enumerations, ranges, sizes, and display hints.
- Readability and writability declared by the MIB.

A MIB does **not** explain how to obtain application data. MIBGen therefore separates generated
schema code from engineer-written data collection:

```text
MIB files
   │
   ▼
MIBGen schema and typed Rust models
   │
   ├── generated validation, OID/index encoding, and publication
   │
   └── engineer-owned collectors and application integration
                         │
                         ▼
                  Running SNMP agent
```

MIBGen produces a project that compiles and runs before collectors are implemented. Unfinished
tables are registered with no rows. Unfinished scalars without a usable MIB default are registered
without a `.0` instance.

For SNMPv2c and SNMPv3:

- An exact GET for a claimed but absent instance returns `noSuchInstance`.
- GET-NEXT and GET-BULK skip absent instances.
- An object not claimed by any provider returns `noSuchObject`.

SNMPv1 uses its protocol-level `noSuchName` behavior.

### Supported selections

MIBGen can generate:

- A complete conceptual table, including its index model and accessible columns.
- An OID group or subtree containing accessible scalars and tables.
- An SMIv2 `OBJECT-GROUP`, limited to the objects in its `OBJECTS` clause.
- Multiple independent targets from one or more entry MIB files.

MIBGen rejects individual scalar selections, notifications, compliance statements, ambiguous
symbols, unresolved imports, overlapping selections, and unsupported index syntax.

### Built-in providers

The Rust agent already implements common standard MIB objects, including:

- MIB-II system and interfaces.
- MIB-II address translation, IP, TCP, and UDP.
- IF-MIB `ifXTable`.
- IP-MIB `ipAddressTable`.
- Host Resources system, storage, device, partition, filesystem, running-software, performance,
  and installed-software objects.
- SNMPv3 USM user management.
- VACM security-to-group and access tables.

When a selected target is fully built in, MIBGen records and enables the runtime feature but does
not create a duplicate provider skeleton. A mixed subtree produces code only for the custom
remainder. Built-in provider reuse prevents two providers from claiming the same OIDs.

## Install MIBGen

MIBGen is distributed for 64-bit Windows and Linux as well as Apple silicon and Intel macOS.
Every installer includes the safe-Rust agent developer kit, built-in providers, offline
documentation, and the target-specific binary SNMP SDK library. Rust itself is not bundled.

Before installing, compare the package with the accompanying `SHA256SUMS` file:

```text
# macOS
shasum -a 256 -c SHA256SUMS

# Linux
sha256sum -c SHA256SUMS

# Windows PowerShell
(Get-FileHash .\MIBGen-1.0.0-x64-setup.exe -Algorithm SHA256).Hash
```

The Windows and Linux packages in this release are unsigned. Obtain them from a trusted source
and verify their checksum before accepting a Windows SmartScreen warning or marking an AppImage
executable.

### Linux x86-64

For Ubuntu 20.04 or a newer Debian-family system, install the DEB:

```text
sudo apt install ./mibgen_1.0.0_amd64.deb
```

The program is installed in `/usr/bin`, and its agent developer kit is installed under
`/usr/lib/mibgen/agent-kit`. Remove it with:

```text
sudo apt remove mibgen
```

For another compatible desktop distribution, use the AppImage:

```text
chmod +x MIBGen-1.0.0-x86_64.AppImage
./MIBGen-1.0.0-x86_64.AppImage
```

The portable tar archive may be extracted anywhere. Keep `agent-kit` beside the `MIBGen`
executable:

```text
tar -xzf MIBGen-1.0.0-x86_64-linux.tar.gz
./MIBGen-1.0.0-x86_64-linux/MIBGen
```

### Windows x86-64

Run `MIBGen-1.0.0-x64-setup.exe`. The default installation is for the current user, does not
require administrator access, and creates Start Menu and uninstall entries. Windows SmartScreen
may display an unrecognized-app warning because this milestone is unsigned. Verify the checksum
before selecting **More info** and **Run anyway**.

Alternatively, extract `MIBGen-1.0.0-x86_64-windows.zip` and run `MIBGen.exe`. Do not move the
executable away from its adjacent `agent-kit` directory. Remove the installed version through
Windows **Installed apps**, or delete the portable directory.

### macOS

Open `MIBGen-1.0.0-universal-apple-darwin.dmg` and drag `MIBGen.app` to Applications. This unsigned
milestone may require Control-clicking the application and selecting **Open** the first time.
Delete the application to uninstall it.

## Requirements

Install:

- Rust 1.85 or newer.
- Cargo from the same Rust toolchain.
- The `rustfmt` component.
- MIBGen and a compatible iReasoning Rust agent developer kit.

MIBGen is available on macOS, Linux, and Windows. An installed distribution may include a
platform-compatible developer kit and binary SNMP SDK library; MIBGen selects it automatically
when present. Otherwise, select a compatible developer-kit directory or use the repository Rust
workspace. MIBGen automatically discovers and validates the Rust tools and runtime at startup.

All imports needed by an entry MIB must be available in the same directory as that entry file.
Different entry MIBs may use different directories. Identical shared modules are deduplicated;
different files declaring the same module name are rejected as a version conflict.

For development, use a loopback address and a non-privileged UDP port. Generated projects default
to `127.0.0.1:1161`.

## The six-step workflow

MIBGen displays all six steps. A control remains disabled until its prerequisites are satisfied.

### Step 1: Choose Project

MIBGen starts in **New Project** mode.

- Select **Open Project…** to open an existing `MIBGenProject.mibgen` document.
- An opened project restores MIB paths, targets, output directory, runtime, configuration path, and
  generated-project state.
- Select **Change…** while an existing project is open to switch project context.
- Switching to New Project clears only the GUI's working state. It does not delete the previous
  project from disk.

Project switching is disabled while generation, synchronization, building, or an agent process is
active.

### Step 2: MIB Sources

Select **Add MIB Files…** to choose one or more entry MIBs. MIB files may have any extension.

MIBGen automatically rescans when a MIB is added, removed, or edited in the path field. The status
line reports the number of entry modules and supported targets.

Use **Add Empty Path** when entering a path manually. Imports are resolved relative to each entry
MIB's directory.

An entry MIB is the file containing:

```text
MODULE-NAME DEFINITIONS ::= BEGIN
```

Do not select every imported file individually. Select the entry MIBs whose targets you want;
MIBGen follows their imports.

### Step 3: Target Selection

Search and select tables, groups, or `OBJECT-GROUP`s.

- Names are module-qualified, for example `ACME-SENSOR-MIB::acmeSensorTable`.
- The target summary lists the number of selected scalars and tables.
- Selected target names appear below the count.
- If two choices overlap, the most recent selection replaces the earlier editable selection.
- A new selection cannot replace a target already included in an opened project.
- **Advanced manual targets** accepts additional `MODULE::target` names when necessary.

In an opened project, existing targets are marked **Included**. Select new targets and then select
**Add Selected Targets**. Only the new difference is submitted; existing provider files are not
rewritten.

### Step 4: Project

For a new project:

1. Choose an output directory that does not exist.
2. Confirm or select the runtime directory.
3. Wait for automatic Rust toolchain validation.
4. Select **Create Project**.

The Cargo package and executable names are derived from the output-directory name.

**Replace an existing output directory** is destructive and requires confirmation. It is for
recreating a project, not for adding targets to an existing project.

For an opened project:

- **Check for MIB Changes** performs a read-only synchronization preview.
- **Synchronize Project** updates generator-owned schema files for targets already in the project.

Target addition belongs to Step 3; synchronization never adds newly selected targets.

### Step 5: Build and Run

The Configuration field defaults to `config/SnmpAgent.xml` in the project.

- The pencil icon edits existing attributes in the XML `<properties>` element.
- Boolean `yes`/`no` attributes use switches.
- `engineBoots` is intentionally not editable because the authoritative SNMPv3 engine manages it.
- **Browse…** selects a different XML configuration file.
- **Build Agent** validates registration, compiles a release executable, and assembles `dist/`.
- **Run Agent** launches the built executable with the selected configuration.
- **Stop Agent** terminates the running agent and its child-process group.

The Properties editor does not manage users, VACM groups/views, trap sinks, MIB entries, or other
XML elements. Edit those sections with a text or XML editor. See
[SNMP_AGENT_CONFIG.md](../agent/SNMP_AGENT_CONFIG.md) for the complete configuration reference.

### Step 6: Activity

Activity displays generator, compiler, and agent output.

- Select and copy individual text directly from the result pane.
- **Copy** copies the complete retained output and changes briefly to **Copied**.
- **Clear** removes retained output from the GUI.
- Build cancellation and agent stop operations terminate the complete process group.

The log is bounded so a long-running agent cannot consume memory without limit.

## Tutorial: create an agent

### Beginner end-to-end tutorial

If this is your first generated agent, follow
[Build Your First Rust SNMP Agent with MIBGen](MIBGEN_BEGINNER_AGENT_TUTORIAL.md). That tutorial
creates `/Users/m1/projects/test/example`, implements `rustExampleScalars` and
`rustIETFWGTable`, verifies them with `snmpwalk`, and then adds `rustHostsTable` through MIBGen's
incremental workflow.

The remainder of this section is a shorter reference implementation focused on scalar and hosts
providers.

This tutorial uses:

```text
mibs/RUST-EXAMPLES-MIB.txt
```

and selects:

```text
RUST-EXAMPLES-MIB::rustExampleScalars
RUST-EXAMPLES-MIB::rustHostsTable
```

The scalar group contains two integer scalars. The table maps hostnames to IPv4 or IPv6 addresses.
The finished example reads the operating system's hosts file and refreshes it every five seconds.

### Create the project

1. Start MIBGen. Step 1 should show **Selected: New Project**.
2. In Step 2, select **Add MIB Files…**.
3. Choose `RUST-EXAMPLES-MIB.txt`.
4. Wait for automatic target discovery.
5. In Step 3, select `RUST-EXAMPLES-MIB::rustExampleScalars`.
6. Select `RUST-EXAMPLES-MIB::rustHostsTable`.
7. In Step 4, choose a new output directory, such as:

   ```text
   macOS or Linux: /tmp/rust-hosts-agent
   Windows:        C:\Users\developer\Projects\rust-hosts-agent
   ```

8. Confirm that the Rust toolchain status is ready.
9. Select **Create Project**.

MIBGen saves:

```text
<output>/MIBGenProject.mibgen
```

Open this file through Step 1 in later sessions. It records paths and target names, not XML
configuration contents or credentials.

## Understand the generated project

A current format-v2 project resembles:

```text
rust-hosts-agent/
├── .mibgen/
│   ├── project.json
│   └── runtime/                    # bundled developer kit when applicable
├── config/
│   └── SnmpAgent.xml
├── mibs/                           # copied entry MIBs and import closure
├── src/
│   ├── application.rs
│   ├── main.rs
│   ├── providers/
│   │   ├── rust_examples_mib__rust_example_scalars.rs
│   │   └── rust_examples_mib__rust_hosts_table.rs
│   └── mibgen_generated/
│       ├── mod.rs
│       └── targets/
│           ├── rust_examples_mib__rust_example_scalars.rs
│           └── rust_examples_mib__rust_hosts_table.rs
├── tests/
│   ├── mibgen_generated.rs
│   └── providers/
├── Cargo.toml
├── Cargo.lock
├── MIBGenProject.mibgen
├── README.md
├── build.sh
├── build.ps1
└── schema.json
```

### Engineer-owned files

MIBGen creates these files once and does not overwrite them during Add or Synchronize:

- `src/main.rs`
- `src/application.rs`
- Everything under `src/providers/`
- Everything under `tests/providers/`
- `Cargo.toml`
- `config/SnmpAgent.xml`
- `README.md`
- Build scripts

Put application I/O and business logic in these files.

### Generator-owned files

Do not edit:

- `.mibgen/project.json`
- `schema.json`
- Managed files under `mibs/`
- Everything under `src/mibgen_generated/`
- `tests/mibgen_generated.rs`

The manifest records hashes for generator-owned files. Add and Synchronize stop rather than
discarding detected manual changes.

### Project-wide hooks

`src/application.rs` contains hooks for changes that apply to the complete agent:

```rust
pub fn configure(config: &mut AgentConfig) -> ireasoning_snmp_agent::Result<()>;

pub fn start(
    builder: &AgentBuilder,
    providers: &GeneratedProviders,
) -> ireasoning_snmp_agent::Result<Vec<tokio::task::JoinHandle<()>>>;
```

Use `configure` for programmatic configuration adjustments. Use `start` for application-wide
background jobs. Target-specific code normally belongs in its file under `src/providers/`.

## Implement generated scalars

Open:

```text
src/providers/rust_examples_mib__rust_example_scalars.rs
```

The generated model is:

```rust
pub struct RustExamplesMibScalars {
    pub rust_example_integer: Option<i32>,
    pub rust_example_sleeper: Option<i32>,
}
```

`Option` controls instance presence:

- `Some(value)` publishes the scalar's `.0` instance.
- `None` removes that scalar instance from the published snapshot.

For this MIB, MIBGen recognizes numeric `DEFVAL` values and publishes them during generated
startup. To replace those defaults with application values, implement the engineer-owned file as:

```rust
use crate::mibgen_generated::targets::rust_examples_mib__rust_example_scalars::*;
use ireasoning_snmp_agent::ManagedObjectError;
use std::sync::Arc;

pub fn start(
    providers: Arc<GeneratedProviders>,
) -> ireasoning_snmp_agent::Result<Vec<tokio::task::JoinHandle<()>>> {
    let snapshot = collect_rust_examples_mib_scalars()?;
    providers.publish_rust_examples_mib_scalars(snapshot)?;
    Ok(Vec::new())
}

pub fn collect_rust_examples_mib_scalars() -> Result<RustExamplesMibScalars, ManagedObjectError> {
    Ok(RustExamplesMibScalars {
        rust_example_integer: Some(42),
        rust_example_sleeper: Some(1),
    })
}
```

Replace the literal values with reads from your application state. Return one internally
consistent snapshot.

Do not return made-up fallback values when the application cannot provide a required measurement.
Depending on the object contract, retain the last valid snapshot or return
`ManagedObjectError::Unavailable`.

## Implement a generated table

Open:

```text
src/providers/rust_examples_mib__rust_hosts_table.rs
```

The generated types are:

```rust
pub struct RustHostsTableIndex {
    pub rust_host_name: Vec<u8>,
}

pub struct RustHostsTableRow {
    pub index: RustHostsTableIndex,
    pub rust_host_address_type: Option<i32>,
    pub rust_host_address: Option<Vec<u8>>,
    pub rust_host_storage: Option<i32>,
    pub rust_host_row_status: Option<i32>,
}
```

The index is mandatory. Optional column fields control whether that cell exists. In most normal
tables, publish all readable columns for every row.

The following implementation reads a bounded hosts file, creates one row per hostname, publishes
an initial snapshot, and refreshes it every five seconds:

```rust
use crate::mibgen_generated::targets::rust_examples_mib__rust_hosts_table::*;
use ireasoning_snmp_agent::ManagedObjectError;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

const MAX_HOSTS_FILE_SIZE: u64 = 1024 * 1024;
const REFRESH_INTERVAL: Duration = Duration::from_secs(5);
const INET_ADDRESS_TYPE_IPV4: i32 = 1;
const INET_ADDRESS_TYPE_IPV6: i32 = 2;
const STORAGE_TYPE_NON_VOLATILE: i32 = 3;
const ROW_STATUS_ACTIVE: i32 = 1;

pub fn start(
    providers: Arc<GeneratedProviders>,
) -> ireasoning_snmp_agent::Result<Vec<tokio::task::JoinHandle<()>>> {
    match collect_rust_hosts_table_rows() {
        Ok(rows) => providers.publish_rust_hosts_table(rows)?,
        Err(error) => eprintln!("rustHostsTable starts empty: {error}"),
    }

    let task = tokio::spawn(async move {
        let mut timer = tokio::time::interval(REFRESH_INTERVAL);
        timer.tick().await;
        loop {
            timer.tick().await;
            let collected = tokio::task::spawn_blocking(collect_rust_hosts_table_rows).await;
            match collected {
                Ok(Ok(rows)) => {
                    if let Err(error) = providers.publish_rust_hosts_table(rows) {
                        eprintln!("rustHostsTable refresh rejected: {error}");
                    }
                }
                Ok(Err(error)) => {
                    eprintln!("rustHostsTable collection failed: {error}");
                }
                Err(error) => {
                    eprintln!("rustHostsTable collection task failed: {error}");
                }
            }
        }
    });

    Ok(vec![task])
}

pub fn collect_rust_hosts_table_rows() -> Result<Vec<RustHostsTableRow>, ManagedObjectError> {
    let file = File::open(hosts_file()).map_err(|_| ManagedObjectError::Unavailable)?;
    let mut bytes = Vec::new();
    file.take(MAX_HOSTS_FILE_SIZE + 1)
        .read_to_end(&mut bytes)
        .map_err(|_| ManagedObjectError::Unavailable)?;
    if bytes.len() as u64 > MAX_HOSTS_FILE_SIZE {
        return Err(ManagedObjectError::Unavailable);
    }
    let text = String::from_utf8(bytes).map_err(|_| ManagedObjectError::Unavailable)?;
    Ok(rows_from_hosts_text(&text))
}

fn rows_from_hosts_text(text: &str) -> Vec<RustHostsTableRow> {
    let mut hosts = BTreeMap::<Vec<u8>, (i32, Vec<u8>)>::new();
    for line in text.lines() {
        let data = line.split_once('#').map_or(line, |(data, _)| data);
        let mut fields = data.split_whitespace();
        let Some(address) = fields.next().and_then(|value| value.parse::<IpAddr>().ok()) else {
            continue;
        };
        let (address_type, address) = match address {
            IpAddr::V4(value) => (INET_ADDRESS_TYPE_IPV4, value.octets().to_vec()),
            IpAddr::V6(value) => (INET_ADDRESS_TYPE_IPV6, value.octets().to_vec()),
        };
        for hostname in fields {
            let hostname = hostname.as_bytes();
            if hostname.is_empty() || hostname.len() > 64 {
                continue;
            }
            hosts
                .entry(hostname.to_vec())
                .or_insert_with(|| (address_type, address.clone()));
        }
    }

    hosts
        .into_iter()
        .map(|(hostname, (address_type, address))| RustHostsTableRow {
            index: RustHostsTableIndex {
                rust_host_name: hostname,
            },
            rust_host_address_type: Some(address_type),
            rust_host_address: Some(address),
            rust_host_storage: Some(STORAGE_TYPE_NON_VOLATILE),
            rust_host_row_status: Some(ROW_STATUS_ACTIVE),
        })
        .collect()
}

#[cfg(windows)]
fn hosts_file() -> PathBuf {
    let root = std::env::var_os("SystemRoot").unwrap_or_else(|| "C:\\Windows".into());
    PathBuf::from(root).join("System32\\drivers\\etc\\hosts")
}

#[cfg(not(windows))]
fn hosts_file() -> PathBuf {
    PathBuf::from("/etc/hosts")
}
```

### Why collection uses a background task

The agent processes requests concurrently. Provider `get` callbacks must remain fast and
thread-safe. They must not perform filesystem, database, shell, or network collection while
handling an SNMP request.

The example:

1. Collects outside request processing.
2. Uses `spawn_blocking` for filesystem work.
3. Produces a complete typed row snapshot.
4. Calls `publish_rust_hosts_table`.
5. Retains the previous valid snapshot if collection or validation fails.

The agent aborts target startup tasks returned by `start` during shutdown.

### Atomic publication

The generated publication method:

```rust
providers.publish_rust_hosts_table(rows)?;
```

validates:

- Index types and constraints.
- Duplicate indexes.
- Column syntax, ranges, sizes, and enumerations.
- Selected column ownership.
- Provider and instance limits.
- Global OID conflicts.

It then atomically replaces the table instances and refreshes the global ordered OID index. If
registry refresh fails, the previous snapshot is restored. GET-NEXT and GET-BULK therefore never
traverse a partially rebuilt index.

## Build and run the agent

After editing the engineer-owned providers:

1. Return to MIBGen.
2. Select **Build Agent**.
3. Review compiler and generated registration-test output in Activity.
4. When the build succeeds, select **Run Agent**.

The build performs schema/provider registration validation before replacing `dist/`. Errors such
as unresolved syntax, malformed index metadata, or duplicate provider claims fail during the
build rather than appearing only after the agent starts. Live values are validated when a
provider publishes them.

The running status reports the bound address, normally:

```text
udp://127.0.0.1:1161
```

Stop the agent before rebuilding.

## Test the agent with SNMP

Use an SNMP manager or MIB browser configured with:

| Setting | Development value |
|---|---|
| Host | `127.0.0.1` |
| UDP port | `1161` |
| Version | SNMPv2c |
| Read community | `public` |

Load `RUST-EXAMPLES-MIB.txt` and its imports into the manager for symbolic names.

### Verify generated scalars

GET:

```text
RUST-EXAMPLES-MIB::rustExampleInteger.0
RUST-EXAMPLES-MIB::rustExampleSleeper.0
```

Expected tutorial values:

```text
rustExampleInteger.0 = 42
rustExampleSleeper.0 = 1
```

The `.0` suffix is the scalar instance identifier.

### Verify the table

Run GET-SUBTREE or walk:

```text
RUST-EXAMPLES-MIB::rustHostsTable
```

Rows depend on the local hosts file. Confirm:

- Each hostname has a unique table index.
- IPv4 rows use `rustHostAddressType = ipv4(1)`.
- IPv6 rows use `rustHostAddressType = ipv6(2)`.
- Address bytes correspond to the indexed hostname.
- Storage and row status report the configured enum values.

### Verify ordered traversal

Run GET-NEXT starting before the custom enterprise subtree and GET-BULK with several repetitions.
Returned OIDs must increase lexicographically without duplicates. The custom providers and
built-in providers share one ordered registry.

### Verify built-in objects

The generated development configuration enables built-in providers. Verify examples such as:

```text
RFC1213-MIB::sysDescr.0
RFC1213-MIB::ifNumber.0
RFC1213-MIB::ifDescr
IF-MIB::ifXTable
HOST-RESOURCES-MIB::hrSystemUptime.0
```

Available rows depend on the host operating system.

### Test the provider code

Add deterministic parser and publication tests to the target's engineer-owned test file or as a
`#[cfg(test)]` module in the provider. Tests should use synthetic text or application records, not
the developer machine's current interfaces, processes, or hosts file.

At minimum, test:

- Empty input.
- Valid IPv4 and IPv6 rows.
- Duplicate indexes.
- Malformed input.
- Maximum string lengths.
- Values outside MIB constraints.
- Successful publication and failed-publication rollback.

For the tutorial collector, add this module to the bottom of the engineer-owned table provider:

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_ipv4_ipv6_comments_and_duplicate_names() {
        let rows = rows_from_hosts_text(
            "127.0.0.1 localhost loopback\n\
             ::1 localhost ip6-localhost # comment\n\
             malformed ignored\n",
        );
        assert_eq!(rows.len(), 3);

        let localhost = rows
            .iter()
            .find(|row| row.index.rust_host_name == b"localhost")
            .unwrap();
        assert_eq!(
            localhost.rust_host_address_type,
            Some(INET_ADDRESS_TYPE_IPV4)
        );
        assert_eq!(localhost.rust_host_address, Some(vec![127, 0, 0, 1]));
    }
}
```

The first `localhost` row is retained because a MIB table cannot publish two rows with the same
index.

## Add another target

To extend an existing agent:

1. Open `MIBGenProject.mibgen`.
2. Add any new entry MIB files in Step 2.
3. Select new targets in Step 3.
4. Confirm existing targets are marked **Included**.
5. Select **Add Selected Targets**.
6. Implement the newly created engineer-owned provider files.
7. Build and test again.

MIBGen submits only targets not already in the manifest. Existing files under `src/providers/` and
`tests/providers/` remain byte-for-byte unchanged.

Target removal is not currently part of the GUI workflow.

## Synchronize a revised MIB

Use synchronization when the definition of an existing target changes.

1. Commit or back up the working project.
2. Open its `MIBGenProject.mibgen`.
3. Update the entry MIB path or replace the MIB with the revised version.
4. Select **Check for MIB Changes**.
5. Review changes to modules, indexes, columns, syntax, and access modes in Activity.
6. Select **Synchronize Project**.
7. Update engineer-owned provider code if generated types changed.
8. Build and run all provider tests.

Check is read-only. Synchronize updates only generator-owned files and managed MIB copies.

Synchronization stops without modifying the project if:

- A target disappeared or became ambiguous.
- An import cannot be resolved.
- An index became unsupported.
- Target ownership now overlaps.
- A generator-owned file was edited manually.

Compilation after a successful synchronization identifies manual code that needs adaptation.

## Provider development rules

### Provider checklist

Before considering a provider complete:

- [ ] Derive row identities from stable application identities.
- [ ] Publish unique indexes that satisfy the MIB's constraints.
- [ ] Return correct `Value` variants and enum numbers.
- [ ] Publish a complete, internally consistent snapshot.
- [ ] Bound file, command, database, and network collection.
- [ ] Keep request-time `get` paths fast and free of blocking I/O.
- [ ] Move slow work to background tasks.
- [ ] Retain the previous valid snapshot after transient collection failure.
- [ ] Avoid logging credentials, process arguments, private data, or entire command output.
- [ ] Add deterministic unit and publication tests.
- [ ] Test GET, GET-NEXT, GET-BULK, missing instances, and end-of-MIB behavior.
- [ ] Document platform-specific unavailable values.

### Thread safety

The agent uses a multithreaded Tokio runtime and processes up to `maxConcurrentRequests` requests
simultaneously. Generated and custom providers implement `Send + Sync`.

Use:

- Immutable values behind `Arc`.
- `RwLock` for read-heavy mutable snapshots.
- `Mutex` for short exclusive state changes.
- Atomics for simple independent counters.
- Atomic `publish_*` methods for topology replacement.

Do not hold a lock while performing slow I/O. Collect first, validate and construct the new
snapshot, then acquire only the locks required to publish it.

### Stable indexes

An SNMP table index is part of the management API. Avoid assigning indexes from temporary vector
positions when rows may reorder. Prefer:

- Native operating-system IDs.
- Persistent database IDs.
- Stable device or application identifiers.
- A process-lifetime or persisted allocation map when no native integer exists.

Duplicate index values are rejected.

### Composite and `IMPLIED` indexes

Generated index structs contain one field per `INDEX` component in MIB order. Construct each field
with the generated Rust type. The generated conversion code handles length prefixes and a final
`IMPLIED` component.

Do not manually append OID arcs or encode string lengths. Use the generated index and row types.

### `AUGMENTS`

For an augmented table, generated metadata inherits the base row's index. Publish corresponding
augmented rows with the same index values as the base table. If the MIB requires one-to-one row
existence, create both snapshots from the same application inventory.

### Absent cells and rows

Use `None` only when absence is semantically allowed. Do not use `None` to hide a collection bug.
Removing a table row requires publishing a complete new snapshot without that row.

### Error handling

Use typed `ManagedObjectError` values:

- `Unavailable` for temporary collection failure.
- `InvalidValue` for application data that cannot satisfy the MIB.
- `Limit` when a configured resource bound is exceeded.
- `Internal` only when the failure cannot be represented more specifically.

Do not use `todo!`, `unimplemented!`, or `panic!` on runtime paths.

### SET support

Generated providers are operationally read-only even when the MIB declares `read-write` or
`read-create`.

- SNMPv1 returns `readOnly`.
- SNMPv2c and SNMPv3 return `notWritable`.

Writable support requires explicit transactional hooks:

1. Validate every assignment.
2. Prepare all affected providers without changing live state.
3. Commit only after all assignments prepare successfully.
4. Undo earlier commits if a later provider fails.
5. Persist the change atomically when it must survive restart.
6. Refresh topology only after a successful row create/delete transaction.

See [AGENT_ARCHITECTURE.md](../agent/AGENT_ARCHITECTURE.md) before implementing SET or RowStatus.

## MIB syntax and Rust values

Generated row and scalar fields use convenient Rust primitives. Generated conversion code maps
them to SNMP values.

| Resolved MIB syntax | Generated field | SNMP `Value` |
|---|---|---|
| `INTEGER`, `Integer32`, enum | `i32` | `Value::Integer` |
| `Unsigned32`, `Gauge32` | `u32` | `Value::Gauge32` |
| `Counter32` | `u32` | `Value::Counter32` |
| `Counter64` | `u64` | `Value::Counter64` |
| `TimeTicks` | `u32` | `Value::TimeTicks` |
| `OCTET STRING`, `DisplayString`, `BITS` | `Vec<u8>` | `Value::OctetString` |
| `OBJECT IDENTIFIER` | `Oid` | `Value::ObjectIdentifier` |
| `IpAddress` | `[u8; 4]` or generated address form | `Value::IpAddress` |
| `Opaque` | `Vec<u8>` | `Value::Opaque` |

Always inspect the actual generated type. Textual conventions can change the resolved type,
constraints, display hint, or enum mapping.

MIBGen and the runtime validate:

- Signed and unsigned ranges.
- `SIZE` constraints.
- Enumerated integer values.
- OID and IP-address structure.
- Table-index encoding.

The management agent should publish protocol units declared by the MIB, not presentation units
shown by a MIB browser.

## Agent configuration reference

MIBGen generates `config/SnmpAgent.xml`. The same configuration format is used on macOS, Linux,
and Windows. In Step 5, the pencil button edits existing attributes of the `properties` element.
`engineBoots` is intentionally excluded because the agent manages it. Edit elements such as
`user`, `group`, `view`, `trapSink`, and `snmpV3TrapSink` with a text or XML editor, save the file,
and restart the agent.

The root element must be `SnmpAgent`, and it must contain one direct `properties` child. Other
supported direct children are:

- `user`
- `view`
- `group`
- `vacmSecurityToGroup` and `vacmAccess` in agent-managed blocks
- `mibDirectory`
- `mib`
- `trapSink`
- `snmpV3TrapSink`

Element order is not significant. The file is UTF-8 XML. Escape XML-sensitive characters in
attribute values, such as `&amp;`, `&quot;`, `&lt;`, and `&gt;`.

### Loading, paths, and persistence

Generated agents load the selected configuration as a file-backed configuration:

- Relative `mibDirectory` and `mib` paths are resolved against the XML file's directory.
- At startup, the agent advances `engineBoots` and atomically rewrites the file.
- Dynamically managed USM and VACM rows are also written atomically to this file.
- The process therefore needs read/write access to the file and create/rename access in its
  directory.

Do not share one writable configuration file between agent processes. Do not edit an
agent-managed block while the agent is running. Back up the file before remote USM or VACM
provisioning.

### Value conventions

| Kind | Accepted form |
|---|---|
| Boolean | `yes`, `no`, `true`, or `false`, case-insensitively |
| Duration | Decimal milliseconds |
| OID | Dotted decimal, with an optional leading dot |
| Hexadecimal octets | Compact hex or bytes separated by spaces, colons, or hyphens |
| Community list | Comma- or semicolon-separated values |
| Names | Length limits are measured in UTF-8 octets |

Unknown Java-compatible attributes are generally retained and ignored. This makes migration
easier, but a misspelled Rust setting may silently leave its default in effect.

### The `properties` element

Only the first direct `properties` element is used.

#### Listener, protocol, and communities

| Attribute | Default | Valid values | Purpose |
|---|---:|---|---|
| `version` | `3` | `1`, `2`, `3` | Highest accepted version. `2` accepts v1 and v2c but not v3. |
| `ipAddress` | empty | Numeric IPv4 or IPv6 | Local bind address. Empty means `0.0.0.0`; hostnames are not accepted. |
| `port` | `161` | `0..65535` | UDP port. `0` asks the OS for a temporary port. |
| `maxPacketSize` | `32768` | At least `484` | Maximum request and encoded-response size in bytes. |
| `maxConcurrentRequests` | `64` | `1..4096` | Maximum active request tasks. `1` is sequential. |
| `readCommunity` | empty | Community list | Allows GET, GET-NEXT, and GET-BULK. Empty denies community reads. |
| `writeCommunity` | empty | Community list | Allows SET. Empty denies community writes. |

Each community is limited to 255 bytes and comparisons are constant-time. Communities remain
plaintext in XML even though Rust debug output redacts them. SNMPv3 is accepted only when
`version="3"`.

The UDP dispatcher pauses application-level reads at `maxConcurrentRequests`. Short bursts wait
in the operating-system receive buffer; excess UDP packets can be dropped when that buffer fills.
Responses may complete out of order and are correlated by SNMP identifiers.

#### Authoritative SNMPv3 engine

| Attribute | Default | Valid values | Purpose |
|---|---:|---|---|
| `engineID` | generated | 5–32 hexadecimal octets | Stable authoritative engine identifier. |
| `engineBoots` | `1` | `0..2147483647` | Last persisted engine boot count. |

When `engineID` is empty, the runtime creates a deterministic enterprise-format ID using
iReasoning PEN 15145 and `system.sysName`. Keep the engine ID stable: changing it changes USM key
localization and can invalidate manager-side localized keys.

A file-backed agent increments and persists `engineBoots` before binding. A value already at the
RFC maximum prevents startup. MIBGen deliberately does not offer `engineBoots` in its properties
editor.

#### INFORM delivery

| Attribute | Default | Valid values | Purpose |
|---|---:|---|---|
| `informTimeout` | `1000` | `1..300000` | Wait time for one INFORM response in milliseconds. |
| `informRetries` | `3` | `0..100` | Retransmissions after the initial INFORM. |

For Java compatibility, these attributes may be on the root `SnmpAgent` element. A value on
`properties` takes precedence. Configured notification fan-out uses at most 16 concurrent
deliveries.

#### MIB-II system values

| Attribute | Default | Returned object |
|---|---|---|
| `system.sysDescr` | Platform and architecture description | `sysDescr.0` |
| `system.sysObjectID` | `.1.3.6.1.4.1.15145.1.1` | `sysObjectID.0` |
| `system.sysContact` | empty | `sysContact.0` |
| `system.sysName` | Host name, then `localhost` | `sysName.0` |
| `system.sysLocation` | empty | `sysLocation.0` |
| `system.sysServices` | `72` | `sysServices.0`, range `0..127` |

The system group is operationally read-only. SET does not persist Java-style changes to these
attributes.

#### Built-in provider controls

Every `*.enabled` setting defaults to `yes`. Disable a provider only when custom application code
will claim the same objects.

| Attribute | Default | Valid values | Effect |
|---|---:|---|---|
| `interfaces.enabled` | `yes` | Boolean | Enables `ifNumber`, `ifTable`, and `ifXTable`. |
| `interfaces.refreshIntervalMillis` | `1000` | `100..3600000` | Interface refresh period. |
| `network.addressTranslationEnabled` | `yes` | Boolean | Enables deprecated `atTable`. |
| `network.ipEnabled` | `yes` | Boolean | Enables RFC1213 IP data and IP-MIB address inventory. |
| `network.tcpEnabled` | `yes` | Boolean | Enables TCP scalars and `tcpConnTable`. |
| `network.udpEnabled` | `yes` | Boolean | Enables UDP scalars and `udpTable`. |
| `network.refreshIntervalMillis` | `5000` | `100..3600000` | Network refresh period. |
| `network.collectionTimeoutMillis` | `10000` | `1000..60000` | Limit for platform network commands. |
| `usmUsers.enabled` | `yes` | Boolean | Exposes writable `usmUserSpinLock` and `usmUserTable`. |
| `vacm.enabled` | `yes` | Boolean | Exposes and enforces VACM for SNMPv3 USM. |
| `storage.enabled` | `yes` | Boolean | Enables storage, disk, partition, and filesystem data. |
| `storage.refreshIntervalMillis` | `5000` | `100..3600000` | Storage refresh period. |
| `hostSystem.enabled` | `yes` | Boolean | Enables the seven RFC 2790 `hrSystem` scalars. |
| `hostSystem.refreshIntervalMillis` | `5000` | `100..3600000` | Host-system refresh period. |
| `runningSoftware.enabled` | `yes` | Boolean | Enables running-software and performance tables. |
| `runningSoftware.refreshIntervalMillis` | `5000` | `100..3600000` | Process refresh period. |
| `runningSoftware.includeParameters` | `no` | Boolean | Publishes process arguments after `argv[0]`. |
| `installedSoftware.enabled` | `yes` | Boolean | Enables installed-software inventory. |
| `installedSoftware.refreshIntervalMillis` | `900000` | `100..3600000` | Product scan period. |
| `installedSoftware.scanTimeoutMillis` | `60000` | `1000..300000` | Limit for a product scan. |

Process parameters can contain sensitive data. Leave
`runningSoftware.includeParameters="no"` unless managers are explicitly authorized to see them.

#### Registry limits

| Attribute | Default | Purpose |
|---|---:|---|
| `maxProviders` | `256` | Maximum registered provider count. |
| `maxObjectsPerProvider` | `4096` | Maximum object claims for one provider. |
| `maxInstancesPerProvider` | `250000` | Maximum instances published by one provider. |
| `maxManagedInstances` | `1000000` | Maximum instances in the complete registry. |
| `maxTraversalCandidates` | `1000000` | Maximum candidates examined by one traversal. |

All limits must be positive. A provider refresh that exceeds a limit keeps the last valid
snapshot.

### The `user` element

An ordinary `user` defines one permanent SNMPv3 USM user and maps its security name to a VACM
group:

```xml
<user
  name="operator"
  auth="SHA256"
  authPassword="replace-this"
  priv="AES256"
  privPassword="replace-this"
  group="operators" />
```

| Attribute | Required | Meaning |
|---|---|---|
| `name` | yes | Unique user/security name, 1–32 UTF-8 octets. |
| `group` | yes | Name of an existing `group`. |
| `auth` | by group | Authentication protocol. |
| `authPassword` | by group | Authentication password. |
| `priv` | no | Privacy protocol; defaults to DES when privacy is required. |
| `privPassword` | no | Privacy password; defaults to `authPassword` for `authPriv`. |

Authentication names are `MD5`, `SHA`/`SHA1`, `SHA224`, `SHA256`, `SHA384`, and `SHA512`.
Privacy names are `DES`, `3DES`/`TripleDES`, `AES`/`AES128`, `AES192`, and `AES256`. Names are
case-insensitive and punctuation-insensitive.

The referenced group's security level determines which credentials are active:

| Group security level | Requirement |
|---|---|
| `noAuthNoPriv` | Authentication and privacy attributes are ignored. |
| `authNoPriv` | `auth` and `authPassword` are required. |
| `authPriv` | Authentication is required; privacy defaults described above apply. |

Ordinary users accept passwords, not `authKey` or `privKey`. Duplicate names, unknown groups,
missing credentials, or unsupported protocols stop configuration loading. Passwords are
zeroizing and redacted in memory, but they are plaintext in the XML file.

#### Agent-managed USM users

Users created or changed through `usmUserTable` are persisted in a managed block:

```xml
<!-- BEGIN IREASONING RUST MANAGED USM USERS -->
<user
  name="provisionedUser"
  cloneFrom="operator"
  status="active"
  publicHex="">
  <authKeyChange value="..." />
  <privKeyChange value="..." />
</user>
<!-- END IREASONING RUST MANAGED USM USERS -->
```

Managed attributes include `cloneFrom`, `status` (`active` or `notInService`), `publicHex`,
`authDisabled`, and `privDisabled`. Key-change children contain RFC 3414 key-change values, not
plaintext passwords or localized keys. Do not hand-edit this block while the agent runs.

### The `view` element

A view is one VACM view-tree family:

```xml
<view
  name="systemOnly"
  type="included"
  subTree=".1.3.6.1.2.1.1"
  mask="" />
```

| Attribute | Required | Default | Meaning |
|---|---|---|---|
| `name` | yes | — | View name, 1–32 UTF-8 octets. |
| `type` | yes | — | `included`/`include` or `excluded`/`exclude`. |
| `subTree` | yes | — | Numeric subtree OID; lowercase `subtree` also works. |
| `mask` | no | empty | Per-subidentifier significance mask. |

Multiple elements with the same name form one logical view. The same name/subtree pair cannot be
repeated. In a mask, `1` means that subidentifier must match and `0` means wildcard; separators
such as `.`, `:`, comma, hyphen, and whitespace are ignored. Missing positions imply `1`, and at
most 128 mask bits are accepted.

An exclusion can override a broader inclusion:

```xml
<view name="systemExceptContact" type="included"
  subTree=".1.3.6.1.2.1.1" mask="" />
<view name="systemExceptContact" type="excluded"
  subTree=".1.3.6.1.2.1.1.4" mask="" />
```

The most specific matching family determines the result. View families are startup XML data;
SNMP modification of `vacmViewTreeFamilyTable` is not implemented.

### The `group` element

A group creates a permanent active VACM access row for USM:

```xml
<group
  name="operators"
  securityLevel="authPriv"
  contextPrefix=""
  match="exact"
  readView="systemOnly"
  writeView="systemOnly"
  notifyView="systemOnly" />
```

| Attribute | Required | Default | Meaning |
|---|---|---|---|
| `name` | yes | — | Unique group name, 1–32 UTF-8 octets. |
| `securityLevel` | yes | — | `noAuthNoPriv`, `authNoPriv`, or `authPriv`. |
| `contextPrefix` | no | empty | Context name or prefix, 0–32 UTF-8 octets. |
| `match` | no | `exact` | `exact` or `prefix`. |
| `readView` | no | empty | View for GET, GET-NEXT, and GET-BULK. |
| `writeView` | no | empty | View for SET. |
| `notifyView` | no | empty | View for configured v3 notifications. |

An empty or undefined view denies that access class. Each ordinary `user group="NAME"` creates a
permanent USM security-to-group mapping. VACM maps a request user to a group, selects the best
active access row for context/model/level, chooses the operation's view, and checks each OID.
Denied exact reads appear missing; traversal skips denied instances; a denied SET returns
`noAccess` and the one-based varbind index.

### Agent-managed VACM elements

Rows created through writable VACM tables are persisted between these comments:

```xml
<!-- BEGIN IREASONING RUST MANAGED VACM -->
...
<!-- END IREASONING RUST MANAGED VACM -->
```

`vacmSecurityToGroup` maps a security identity to a group:

```xml
<vacmSecurityToGroup
  securityModel="3"
  securityName="provisionedUser"
  groupName="operators"
  storageType="nonVolatile"
  status="active" />
```

| Attribute | Required | Default or accepted values |
|---|---|---|
| `securityModel` | yes | `v1`/`1`, `v2c`/`2`, `usm`/`3`, or a positive model |
| `securityName` | yes | 1–32 UTF-8 octets |
| `groupName` | when active | 1–32 UTF-8 octets |
| `storageType` | no | `nonVolatile`; also `volatile`, `2`, or `3` |
| `status` | no | `active`; also `1`, `notInService`, `2`, `notReady`, or `3` |

`vacmAccess` defines a dynamic access row:

```xml
<vacmAccess
  groupName="operators"
  contextPrefix=""
  securityModel="usm"
  securityLevel="authPriv"
  contextMatch="exact"
  readView="systemOnly"
  writeView="systemOnly"
  notifyView="systemOnly"
  storageType="nonVolatile"
  status="active" />
```

| Attribute | Required | Default or accepted values |
|---|---|---|
| `groupName` | yes | 1–32 UTF-8 octets |
| `contextPrefix` | no | Empty; at most 32 UTF-8 octets |
| `securityModel` | yes | Models above plus `any` or `0` |
| `securityLevel` | yes | `noAuthNoPriv`/`1`, `authNoPriv`/`2`, `authPriv`/`3` |
| `contextMatch` | no | `exact`; or `prefix` |
| `readView`, `writeView`, `notifyView` | no | Empty; at most 32 UTF-8 octets |
| `storageType` | no | `nonVolatile`; or `volatile`, `2`, `3` |
| `status` | no | `active`; or `notInService`, `notReady`, `1`, `2`, `3` |

Duplicate table indexes are rejected. Ordinary `user` and `group` rows are permanent and cannot
be modified through these writable tables.

### The `mibDirectory` and `mib` elements

Add repeatable import directories and modules:

```xml
<mibDirectory path="../mibs" />
<mibDirectory path="../vendor-mibs" />
<mib path="ACME-SENSOR-MIB" />
<mib path="../mibs/ACME-ALARM-MIB.mib" />
```

The `path` attribute is required. A `mib` path is tried directly, then in each directory. When no
extension is present, the loader tries `.mib`, `.MIB`, `.my`, and `.txt`, then performs a
case-insensitive filename/stem search.

Configured modules use strict full-depth parsing with syntax resolution. Missing imports,
ambiguous symbols, or unresolved syntax stop startup. A MIB supplies schema only; a Rust provider
must still publish instances. Generated projects normally append their managed entry MIB paths in
code, so do not add duplicate entries merely because the files appear under the project `mibs`
directory.

### The `trapSink` element

Configure an SNMPv1 or SNMPv2c destination:

```xml
<trapSink
  hostname="192.0.2.10"
  port="162"
  community="public"
  version="2"
  isInform="no"
  enabled="yes" />
```

| Attribute | Required | Default | Meaning |
|---|---|---|---|
| `hostname` | yes | — | Destination hostname/IP, resolved when sending. |
| `port` | no | `162` | Nonzero UDP destination port. |
| `community` | no | `public` | Community, at most 255 bytes. |
| `version` | no | `1` | `1` for v1 Trap-PDU or `2` for v2c. |
| `isInform` | no | `no` | Use INFORM when `yes`; invalid with version 1. |
| `enabled` | no | `yes` | Disabled sinks remain configured but are skipped. |

Traps are sent once. INFORMs use `informTimeout` and `informRetries`. During v2-to-v1 conversion,
Counter64 varbinds are removed. Two community sinks cannot have the same normalized hostname,
port, version, and trap/INFORM mode, even if their communities differ.

### The `snmpV3TrapSink` element

Configure an SNMPv3 trap or INFORM destination:

```xml
<snmpV3TrapSink
  hostname="192.0.2.20"
  port="162"
  userName="notifyUser"
  auth="SHA256"
  authPassword="replace-this"
  priv="AES256"
  privPassword="replace-this"
  contextName=""
  isInform="no"
  enabled="yes" />
```

| Attribute | Required | Default | Meaning |
|---|---|---|---|
| `hostname` | yes | — | Destination hostname or IP address. |
| `port` | no | `162` | Nonzero UDP port. |
| `userName` | yes | — | USM name, 1–32 UTF-8 octets. |
| `auth` | by level | absent | Authentication protocol. |
| `authPassword` | for password auth | absent | Authentication password. |
| `authKey` | for localized-key auth | absent | Localized hexadecimal authentication key. |
| `priv` | no | `DES` with privacy | Privacy protocol. |
| `privPassword` | for password privacy | absent | Privacy password. |
| `privKey` | for localized-key privacy | absent | Localized hexadecimal privacy key. |
| `engineID` | for raw keys | absent | Engine against which the keys were localized. |
| `contextName` | no | empty | Scoped-PDU context. |
| `isInform` | no | `no` | Selects INFORM instead of trap. |
| `enabled` | no | `yes` | Whether fan-out uses this sink. |

Security level is inferred from supplied material: no auth fields means `noAuthNoPriv`; auth
material means `authNoPriv`; auth plus privacy material means `authPriv`. Privacy without
authentication is rejected. Password and localized-key forms cannot be mixed. `authKey` requires
`auth`, `privKey` requires `authKey`, and raw keys require `engineID`.

Localized authentication key lengths are MD5 16, SHA-1 20, SHA-224 28, SHA-256 32, SHA-384 48,
and SHA-512 64 bytes. Privacy key lengths are DES/AES-128 16, AES-192 24, and
3DES/AES-256 32 bytes.

For a trap, the local agent is authoritative, so a raw key must be localized to the local
`engineID`. For an INFORM, the receiver is authoritative and `engineID` seeds or validates
discovery. The sink user's active VACM `notifyView` is evaluated before configured v3 delivery.
Missing mappings, inactive rows, empty/undefined views, excluded notification OIDs, and context
mismatches skip the sink.

Two v3 sinks cannot have the same normalized hostname, port, user name, and trap/INFORM mode.
Declaring a sink does not create notifications automatically; application code initiates delivery
through the agent notifier.

### Authorization summary

For SNMPv1/v2c, read operations require `readCommunity`, SET requires `writeCommunity`, and VACM
does not apply. For SNMPv3, the message needs an active USM user at exactly its configured security
level. When VACM is enabled, its mapping, access row, context, and selected view authorize every
OID. Application-installed policies can restrict further but cannot override a VACM denial.

### Complete SNMPv3 configuration example

```xml
<?xml version="1.0" encoding="UTF-8"?>
<SnmpAgent>
  <properties
    version="3"
    ipAddress="127.0.0.1"
    port="1161"
    maxPacketSize="32768"
    maxConcurrentRequests="64"
    readCommunity=""
    writeCommunity=""
    engineID="80003b29050102030405060708"
    engineBoots="1"
    informTimeout="1000"
    informRetries="3"
    system.sysDescr="ACME Rust SNMP Agent"
    system.sysObjectID=".1.3.6.1.4.1.99999.1"
    system.sysName="acme-agent"
    interfaces.enabled="yes"
    network.addressTranslationEnabled="yes"
    network.ipEnabled="yes"
    network.tcpEnabled="yes"
    network.udpEnabled="yes"
    storage.enabled="yes"
    hostSystem.enabled="yes"
    runningSoftware.enabled="yes"
    installedSoftware.enabled="yes"
    usmUsers.enabled="yes"
    vacm.enabled="yes" />

  <view name="readView" type="included"
    subTree=".1.3.6.1.2.1" mask="" />
  <view name="adminView" type="included"
    subTree=".1.3.6.1" mask="" />

  <group name="readers"
    securityLevel="authNoPriv"
    readView="readView"
    writeView=""
    notifyView="" />
  <group name="administrators"
    securityLevel="authPriv"
    readView="adminView"
    writeView="adminView"
    notifyView="adminView" />

  <user name="monitor"
    auth="SHA256"
    authPassword="replace-this"
    group="readers" />
  <user name="administrator"
    auth="SHA512"
    authPassword="replace-this"
    priv="AES256"
    privPassword="replace-this"
    group="administrators" />

  <mibDirectory path="../mibs" />
  <mib path="ACME-SENSOR-MIB" />

  <trapSink
    hostname="127.0.0.1"
    port="1162"
    community="replace-this"
    version="2"
    isInform="no" />
  <snmpV3TrapSink
    hostname="127.0.0.1"
    port="1162"
    userName="administrator"
    auth="SHA512"
    authPassword="replace-this"
    priv="AES256"
    privPassword="replace-this"
    contextName=""
    isInform="no" />
</SnmpAgent>
```

This example deliberately uses loopback and non-privileged ports. Replace every placeholder
secret, engine ID, enterprise OID, address, and access view before production use.

### Java configuration compatibility

The Rust agent supports Java-style `properties`, `user`, `group`, `view`, `trapSink`, and
`snmpV3TrapSink` structures. These legacy Java settings are accepted but have no Rust effect:

```text
encryptPasswordAndCommunity
useThreadPool
maxThreadCount
reloadConfigOID
authTrapEnabled
masterAgentPort
remoteMasterAgentPort
remoteMasterAgentIpAddress
subagentIpAddresses
managerIpAddresses
communityViewEnabled
underCreationTimeWindow
```

The `communityView`, `proxy`, and `trapProxy` elements are also not implemented. In particular,
the Rust agent does not apply Java thread-pool settings, encrypt XML secrets, reload through an
OID, create authentication-failure traps automatically, or enable master/subagent/proxy behavior.

For a low-level man-page version of this reference, including persistence recovery and diagnostic
details, see [SNMP_AGENT_CONFIG.md](../agent/SNMP_AGENT_CONFIG.md).

## Troubleshooting

### Create Project is disabled

Check:

- At least one valid entry MIB is selected.
- Automatic target discovery completed successfully.
- At least one non-overlapping target is selected.
- The output directory is set.
- The Rust toolchain and runtime were detected.
- No build, generation, synchronization, or agent job is active.

### A MIB import cannot be resolved

Place the imported module beside the entry MIB. Confirm the ASN.1 module name, not only the
filename. Do not rename two different module versions to the same name.

### A target is missing

MIBGen does not offer individual scalars, notifications, compliance statements, inaccessible
objects, ambiguous symbols, or targets with unsupported index syntax. Review Activity for strict
parser diagnostics.

### Targets overlap

A broad subtree and a contained table or `OBJECT-GROUP` cannot both own the same object. Select
either the broad target or the more specific targets.

### No provider file was generated

The target may already be implemented by a built-in provider. Confirm that it is recorded in
`.mibgen/project.json` and that the corresponding XML feature is enabled.

### The agent returns `noSuchInstance`

The provider is registered but has not published that instance. Check:

- The target `start` hook calls its collector and `publish_*`.
- The collector returns the expected rows or scalar `Some` values.
- Table indexes match the requested instance suffix.
- Publication did not report a validation error.

### Build reports unresolved syntax or schema mismatch

Resolve all imported MIBs and run Check/Synchronize after MIB changes. Registration validation is
expected to catch unresolved syntax, invalid index metadata, and provider/schema disagreements
before producing `dist/`.

### A generator-owned file was modified

Restore it from source control or a clean generated project. Move manual code into
`src/providers/`, `src/application.rs`, or `tests/providers/`.

### Build Agent is disabled

Open or create a valid project and wait for automatic toolchain validation. The project must
contain a valid format-v2 manifest and unmodified generated files.

### Run Agent is disabled

Build successfully first. MIBGen enables Run only when the expected executable exists under
`dist/`.

### The agent cannot bind

Another process may use the configured address and port. Stop the earlier agent or choose another
non-privileged development port.

### SNMP requests time out

Confirm:

- The agent is still running.
- Manager and agent use the same address, port, and version.
- The read community or USM credentials match.
- VACM permits the requested OID and context.
- A host firewall is not blocking UDP.

### A table appears empty

Check Activity for collector errors, then verify the source inventory is nonempty. An empty table
is valid and is skipped by GET-NEXT and GET-BULK.

## Production release checklist

- [ ] Commit or archive the MIB sources used to generate the schema.
- [ ] Commit engineer-owned provider code and tests.
- [ ] Run Check for MIB Changes and review the result.
- [ ] Build with the release developer kit and locked dependencies.
- [ ] Run unit, integration, GET, GET-NEXT, GET-BULK, and negative tests.
- [ ] Replace loopback/development bind settings as required.
- [ ] Replace the `public` community and remove unused community access.
- [ ] Configure SNMPv3 authentication, privacy, and VACM views.
- [ ] Protect configuration files and directories with appropriate permissions.
- [ ] Confirm engine ID and engine-boots persistence behavior.
- [ ] Review `maxPacketSize`, `maxConcurrentRequests`, and registry limits.
- [ ] Confirm every collector is bounded and cannot stall request workers.
- [ ] Confirm indexes remain stable across refresh and restart where required.
- [ ] Confirm no credentials, keys, private application data, or process arguments are logged.
- [ ] Test startup, shutdown, collector failure, malformed requests, and port conflicts.
- [ ] Document platform-specific unavailable objects and counter semantics.
- [ ] Package the executable, configuration, and required MIB files.

## Glossary

| Term | Meaning in MIBGen |
|---|---|
| Entry MIB | A selected file containing a module's `DEFINITIONS ::= BEGIN`; imports are followed automatically. |
| Target | A selected table, OID subtree/group, or `OBJECT-GROUP`. |
| Provider | Rust code that owns MIB objects and supplies live instances and values. |
| Scalar instance | A scalar object's concrete `.0` instance. |
| Table row index | The ordered values that form a table instance suffix. |
| Schema | Parsed MIB structure, syntax, access, indexes, and OIDs. |
| Snapshot | One internally consistent set of scalar values or table rows. |
| Publication | Validating and atomically replacing a provider snapshot. |
| Built-in target | A selected target implemented by the standard agent runtime rather than generated custom code. |
| Engineer-owned file | A file created once that Add and Synchronize never overwrite. |
| Generator-owned file | A deterministic file managed and hash-checked by MIBGen. |
| Add | Introduce new targets without rewriting existing engineer-owned providers. |
| Check | Preview schema synchronization without writing. |
| Synchronize | Regenerate schema-owned files for existing targets after MIB changes. |
| VACM | SNMPv3 View-based Access Control Model. |
| USM | SNMPv3 User-based Security Model. |

For a concise description of GUI controls, see
[MIBGEN_GUI_GUIDE.md](MIBGEN_GUI_GUIDE.md). For deeper runtime and provider internals, see
[AGENT_ARCHITECTURE.md](../agent/AGENT_ARCHITECTURE.md).
