# snmpkit

High-performance SNMP toolkit for Python, powered by Rust.

## Installation

```bash
pip install snmpkit
```

Requires Python 3.11+.

## Quick Start

### SNMP Manager

Query SNMP devices:

```python
import asyncio
from snmpkit.manager import Manager

async def main():
    async with Manager("192.168.1.1", community="public") as mgr:
        # Get system description
        descr = await mgr.get("1.3.6.1.2.1.1.1.0")
        print(f"Device: {descr}")

        # Walk interface table
        async for oid, value in mgr.bulk_walk("1.3.6.1.2.1.2.2.1.2"):
            print(f"{oid} = {value}")

asyncio.run(main())
```

### AgentX Subagent

Extend an SNMP agent with custom data:

```python
from snmpkit.agent import Agent, Updater

class MyUpdater(Updater):
    async def update(self):
        self.set_INTEGER("1.0", 42)
        self.set_OCTETSTRING("2.0", "hello")

agent = Agent()
agent.register("1.3.6.1.4.1.12345", MyUpdater())

# Async (default)
await agent.start()

# Or sync
agent.start_sync()
```

## Manager API Reference

### Manager

```python
Manager(
    host: str,
    port: int = 161,
    # v1/v2c
    community: str = "public",
    version: int = 2,              # 1, 2 (v2c), or 3
    # v3 USM
    user: str | None = None,
    auth_protocol: str | None = None,  # MD5, SHA, SHA224, SHA256, SHA384, SHA512
    auth_password: str | None = None,
    priv_protocol: str | None = None,  # DES, AES128, AES192, AES256
    priv_password: str | None = None,
    context_name: str = "",
    # Common
    timeout: float = 5.0,
    retries: int = 3,
)
```

SNMPv3 usage:

```python
# authPriv
async with Manager(
    "192.168.1.1",
    version=3,
    user="admin",
    auth_protocol="SHA256",
    auth_password="authpass123",
    priv_protocol="AES128",
    priv_password="privpass123",
) as mgr:
    value = await mgr.get("1.3.6.1.2.1.1.1.0")

# authNoPriv
async with Manager(
    "192.168.1.1",
    version=3,
    user="readonly",
    auth_protocol="SHA",
    auth_password="authpass",
) as mgr:
    value = await mgr.get("1.3.6.1.2.1.1.1.0")

# noAuthNoPriv
async with Manager("192.168.1.1", version=3, user="noauth") as mgr:
    value = await mgr.get("1.3.6.1.2.1.1.1.0")
```

Engine discovery is automatic on connect for SNMPv3.

Methods:
- `get(oid: str) -> Value` — Get single OID
- `get_many(*oids: str) -> list[Value]` — Get multiple OIDs
- `get_next(oid: str) -> tuple[str, Value]` — Get next OID
- `get_bulk(*oids, non_repeaters=0, max_repetitions=10) -> list[tuple[str, Value]]` — Bulk get
- `set(oid: str, value: Value) -> None` — Set OID value
- `walk(oid: str) -> AsyncIterator[tuple[str, Value]]` — Walk using GetNext
- `bulk_walk(oid: str, bulk_size=10) -> AsyncIterator[tuple[str, Value]]` — Walk using GetBulk
- `get_table(base_oid, columns=None, bulk_size=25) -> dict[tuple, dict[int, Value]]` — Structured table
- `send_trap(trap_oid, varbinds=None, uptime=None) -> None` — Fire-and-forget trap
- `send_inform(trap_oid, varbinds=None, uptime=None) -> None` — Inform with ACK wait

### TrapReceiver

```python
from snmpkit.manager import TrapReceiver

async with TrapReceiver(port=1162) as receiver:
    async for trap in receiver:
        print(f"{trap.source}: {trap.trap_oid}")
        for oid, value in trap.varbinds:
            print(f"  {oid} = {value}")
```

TrapMessage fields: source, version, community, pdu_type, request_id, trap_oid, uptime, varbinds, raw_varbinds.
InformRequests are automatically acknowledged.

### Concurrent Polling

```python
from snmpkit.manager import PollTarget, poll_many

targets = [PollTarget(host="10.0.0.1"), PollTarget(host="10.0.0.2")]
async for result in poll_many(targets, ["1.3.6.1.2.1.1.1.0"], concurrency=50):
    print(f"{result.target}: {result.value or result.error}")
```

### Exceptions

```python
from snmpkit.manager.exceptions import (
    SnmpError,           # Base
    TimeoutError,        # Request timed out
    NoSuchObjectError,   # OID doesn't exist
    NoSuchInstanceError, # Instance doesn't exist
    EndOfMibViewError,   # End of MIB reached
    GenericError,        # SNMP error with status code
)
```

## Agent API Reference

### Agent

```python
Agent(
    agent_id: str = "snmpkit",
    socket_path: str = "/var/agentx/master",
    parallel_encoding: bool = False,  # Enable Rust parallel encoding
    worker_threads: int = 0,          # Enable Python thread pool
    queue_size: int = 0,              # 0 = unbounded
)
```

Methods:
- `register(oid, updater, freq=10, context=None, priority=127)` — Register OID subtree
- `unregister(oid)` — Unregister OID subtree
- `start()` — Async coroutine
- `start_sync()` — Blocking
- `stop()` — Stop agent

### Updater

```python
class Updater:
    async def update(self) -> None: ...

    # Value setters
    def set_INTEGER(self, oid: str, value: int) -> None
    def set_OCTETSTRING(self, oid: str, value: str | bytes) -> None
    def set_OBJECTIDENTIFIER(self, oid: str, value: str) -> None
    def set_IPADDRESS(self, oid: str, value: str) -> None
    def set_COUNTER32(self, oid: str, value: int) -> None
    def set_GAUGE32(self, oid: str, value: int) -> None
    def set_TIMETICKS(self, oid: str, value: int) -> None
    def set_OPAQUE(self, oid: str, value: bytes) -> None
    def set_COUNTER64(self, oid: str, value: int) -> None
```

### SetHandler

```python
class SetHandler:
    async def test(self, oid: str, value) -> bool: ...
    async def commit(self, oid: str, value) -> None: ...
    async def undo(self, oid: str) -> None: ...
    async def cleanup(self, oid: str) -> None: ...
```

## Low-Level Rust Bindings

```python
from snmpkit.core import Oid, Value, VarBind

# OID manipulation
oid = Oid("1.3.6.1.4.1.12345")
print(oid.parts)        # [1, 3, 6, 1, 4, 1, 12345]
print(oid.parent())     # Oid("1.3.6.1.4.1")
print(oid.child(1))     # Oid("1.3.6.1.4.1.12345.1")

# SNMP values
val = Value.Integer(42)
val = Value.OctetString(b"hello")
val = Value.Counter64(9999999999)
val = Value.IpAddress(192, 168, 1, 1)

# Variable bindings
vb = VarBind(oid, val)
```

## Performance

| Component | Operation | vs Competition |
|-----------|-----------|----------------|
| Manager | BER Encode | 127x faster than pysnmp |
| Manager | GET Request | 8.5x faster than pysnmp |
| Manager | WALK | 14x faster than pysnmp |
| Agent | PDU Encode | 11.5x faster than pyagentx3 |

## Entry Points

| Function | Type | Description |
|----------|------|-------------|
| `snmpkit.run(coro)` | Blocking | Entry point for async apps (wraps uvloop) |
| `agent.start()` | Async | Coroutine, use with `await` |
| `agent.start_sync()` | Blocking | For sync scripts |

Users never need to import uvloop directly.

## Links

- Documentation: https://snmpkit.dev
- GitHub: https://github.com/anthropics/snmpkit
- PyPI: https://pypi.org/project/snmpkit
