A lightweight, self-contained Rust server for Program Opportunity Information Service (POIS) and ESAM (Event Signaling and Management) workflows.
It provides a REST/JSON API, simple web UI, and SQLite persistence for defining channels, rules, and ESAM event logs.
It is also the reference implementation of SCTE 130-9 (SESAME) — Secure ESAM
Authentication and Message Encryption — adding HMAC-SHA256 authentication,
channel-scoped authorization, and AES-256-GCM payload encryption to the ESAM
exchange with measured sub-millisecond per-request overhead
(docs/benchmarks.md). The wire format is specified in
docs/SESAME.md.
- ESAM / SCTE-35 XML processing via REST endpoints
- SESAME (SCTE 130-9) security — three additive, independently-enableable tiers, bidirectional (verifies inbound requests and signs outbound POIS responses), with no ESAM XML schema changes
- SCTE-35 tools (build / decode / validate) — the decoder accepts base64, hex, or binary input
- Front-end web UI served directly from
/static - Channel and Rule management API with bearer authentication
- Automatic SQLite migrations on first start
- Built-in default
defaultchannel +Default nooprule (seeded automatically) - Event logging and monitoring
- TLS optional (via
POIS_TLS_CERT/POIS_TLS_KEY) - Zero external dependencies beyond SQLite and Rust
- One-shot install and uninstall scripts for Ubuntu 24.04
| Variable | Description | Default |
|---|---|---|
POIS_PORT |
Listening port for the web server | 8080 |
POIS_DB |
SQLite database URL (absolute path recommended) | sqlite://pois.db |
POIS_JWT_SECRET |
Secret key for JWT token signing (auto-generated by installer) | required |
POIS_TLS_CERT, POIS_TLS_KEY |
Optional TLS PEM paths | unset |
POIS_SESAME_KEYS |
SESAME signing/encryption key material (JSON, see below). Unset ⇒ SESAME inactive | unset |
POIS_SESAME_MIN_TIER |
Default minimum SESAME tier (0–3) when a channel has no explicit policy | 0 |
POIS_SESAME_REPLAY_WINDOW |
SESAME replay/freshness window, seconds | 300 |
POIS_SESAME_RESPONSE_KEYID |
Signing key-id used to sign POIS responses. Unset ⇒ responses unsigned | unset |
POIS_SESAME_RESPONSE_ENCID |
Encryption key-id for Tier 3 responses | unset |
These are injected automatically by the installer into the systemd unit. POIS_JWT_SECRET is generated fresh on each install using openssl rand.
SESAME secures the ESAM HTTP exchange in three additive tiers, conveyed entirely in HTTP headers (and, for Tier 3, by encrypting the body). The ESAM XML is never modified.
| Tier | Protects against | Mechanism |
|---|---|---|
| 1 Authentication + Integrity | injection, tampering, replay, DoS | HMAC-SHA256 signing, timestamp, nonce |
| 2 Authorization | cross-channel interference | channel-scoped credential binding (X-SESAME-Scope) |
| 3 Encryption | signal reconnaissance | AES-256-GCM payload encryption |
When no POIS_SESAME_* variables are set, the ESAM path behaves exactly as
before (Tier 0 / unauthenticated) — adoption is incremental and backward
compatible. Per-channel minimum tier is stored in channels.sesame_min_tier.
Key configuration (POIS_SESAME_KEYS, distributed out of band per §8.2.5):
{
"signing": [
{"key_id": "sas-east-01", "secret_hex": "…", "channels": ["SportsFeed-East"]},
{"key_id": "pois-primary", "secret_hex": "…", "channels": ["*"]}
],
"encryption": [
{"enc_key_id": "enc-sportsfeed-2026q1", "key_hex": "<64 hex chars = 32 bytes>"}
]
}Measured overhead on AWS c6i.xlarge (Intel Ice Lake, AES-NI + SHA; the
hardware the spec cites — full table and methodology in
docs/benchmarks.md):
| Combined per-request path | p50 (1 KB) | p99 (16 KB) |
|---|---|---|
| Inbound — verify + authz + decrypt | 3.31 µs | 0.027 ms |
| Outbound — encrypt + sign | 4.23 µs | 0.028 ms |
Worst-case combined p99 is 0.028 ms — ~35× inside the 1 ms budget. Pure-Rust
RustCrypto stack (hmac, sha2, aes-gcm), builds everywhere.
- Wire-format spec:
docs/SESAME.md - Reconciliation notes / paper errata:
docs/SESAME_reconciliation.md,docs/SESAME_paper_errata.md - Run the benchmarks:
cargo bench --bench sesame_overhead
sudo apt update
sudo apt install git -y
git clone https://github.com/bokelleher/rust-pois.git
cd rust-pois
sudo ./install.shThe installer will automatically install Rust and build the binary from source. Allow 2-3 minutes for the build to complete.
Follow prompts for:
- service user (default
pois) - port (default
8080)
When complete:
sudo systemctl status poisVisit the UI:
http://<server-ip>:<port>/
make install # wraps: sudo ./install.sh
make uninstall # wraps: sudo ./uninstall.sh
make run-dev # runs a dev server on localhost:18080 using pois.dev.db
make build # cargo build --releaseOverride defaults if needed:
make run-dev DEV_PORT=19000To wipe the SQLite database and reseed the default channel/rule:
sudo ./factory_reset.shThe script will:
- Stop the
poisservice - Delete
/opt/pois/pois.db - Restart the service — migrations + seed will run automatically
| Purpose | Command |
|---|---|
| View logs | sudo journalctl -u pois -f |
| Health check | curl http://localhost:<port>/healthz |
| Database inspect | sqlite3 /opt/pois/pois.db ".tables" |
chmod +x uninstall.sh
sudo ./uninstall.shRemoves the binary, systemd unit, and optional user/data directory.
Planned/proposed enhancements are tracked in ROADMAP.md — including
SMPTE‑timecode/frame‑rate rendering and SCTE‑104 conversion for the SCTE‑35 tools,
human‑readable UPIDs in the Event Monitor, logging of SESAME rejections, and a
distributed (Redis) replay cache for multi‑node SESAME.
MIT License © 2025 Bo Kelleher