Files
rutster/docs/PORT_PLAN.md
adlee-was-taken 0370347642 ADR-0001: SIP strategy — native Rust core behind Kamailio + rtpengine
Record the SIP edge decision and align the docs:
- docs/adr/0001-sip-strategy.md: layered strategy (own Rust parser, rent the
  interop tail via a Kamailio + rtpengine SBC, grow native core behind the shield);
  pjproject FFI explicitly rejected for breaking the memory-safety thesis at the
  most exposed seam.
- PORT_PLAN §1 + open decisions: SIP row updated to the decided strategy.
- ARCHITECTURE: "biggest technical risk" now points at ADR-0001.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2bfD7MkqEdfnMXxXBu456
2026-06-26 21:49:36 -04:00

14 KiB

Rutster — Asterisk → Rust Capability Port Plan

This is the spine of the project. It walks the Asterisk subsystem surface and assigns every capability a disposition in the rutster architecture, with a one-line rationale. The crate/service boundaries fall out of the "Core / Service" rows; the threat model hangs off the "attack surface" of each row.

Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).

Disposition legend

Disposition Meaning
🦀 Core In the hardened, memory-safe Rust core. Reserved for the hot media path, call model, and signaling glue — fast, always-present, trusted.
🧩 Plugin (WASM) Sandboxed extension. Logic that benefits from isolation and third-party extensibility. Cannot crash the core or cross tenant boundaries.
☁️ Service Independent microservice. Stateful, independently-scaled, or business/billing logic. Talks over API + event bus.
🔌 Edge / FFI Wrap mature external C (pjproject/spandsp) or front with an SBC. Used where a pure-Rust rewrite is a tar pit.
Drop Legacy hardware or dead protocol. Gone.
🔁 Replace Capability survives but the mechanism is entirely redesigned (no 1:1 mapping).

Design rules (the heuristics behind every row)

  1. Hot media path & the call model → Core. Trusted, fast, always-on.
  2. Anything that benefits from isolation or third-party extension → Plugin.
  3. Stateful, independently-scaled, or business/billing logic → Service.
  4. Mature C that's a tar pit to rewrite (SIP) → Edge/FFI, with a pure-Rust ambition.
  5. Legacy hardware / dead protocols → Drop.
  6. Security & multi-tenancy are cross-cutting — never a row, always a property.

1. Signaling & channel drivers (channels/, res/res_pjsip*)

Asterisk subsystem Module(s) Disp. Rationale
SIP signaling chan_pjsip + res_pjsip* (48 mods) 🔌 Edge → 🦀 Core Decided (ADR-0001): public SIP edge = Kamailio + rtpengine (proven SBC shield); native Rust parser + transaction/dialog core (rsip/ezk-sip) grows behind it. No pjproject FFI — it would put C at the most exposed seam and break the memory-safety thesis. The single biggest risk item.
↳ registration (in/out) res_pjsip_registrar, _outbound_registration 🦀 Core Registrar state is core control-plane; back it with the state store.
↳ SDP / media negotiation res_pjsip_session, _sdp_rtp 🦀 Core Offer/answer drives the media plane directly.
↳ auth res_pjsip_outbound_authenticator_digest, etc. 🦀 Core Digest + token auth, deny-by-default, rate-limited. Security-critical.
↳ NAT / ICE / rtcp res_pjsip_nat, res_rtp_asterisk ICE 🦀 Core ICE/STUN/TURN via str0m/webrtc-rs.
↳ pub/sub (presence, MWI, BLF) res_pjsip_pubsub, _exten_state, _mwi 🦀 Core + ☁️ Signaling in core; presence/MWI state aggregation as a service.
↳ T.38 fax res_pjsip_t38 ☁️ Service Isolate fax entirely (see §5).
WebRTC signaling chan_websocket, res_http_websocket 🦀 Core First-class ingress. WSS + DTLS-SRTP via the media stack.
External media chan_audiosocket 🦀 Core Modern, relevant pattern (stream media to an external process); keep as a media tap/source API.
Local console audio chan_console 🧩 Plugin Dev/testing convenience; not core.
RTP-only pseudo-channel chan_rtp, chan_bridge_media 🦀 Core Media-plane primitives.
IAX2 chan_iax2 Drop Dead inter-Asterisk protocol. SIP trunks instead.
TDM / analog / ISDN chan_dahdi, sig_analog, sig_pri, sig_ss7 Drop No hardware era. PSTN via SIP trunk / SBC.
H.323 addons/chan_ooh323 Drop Dead protocol.
SCCP / Skinny, MGCP (Skinny/MGCP) Drop Dead protocols.
Unistim chan_unistim Drop Dead vendor protocol.
XMPP / Jingle chan_motif, res_xmpp Drop Dead path.

2. Media plane (main/rtp_engine.c, res_rtp_asterisk, bridges/, main/dsp.c)

Asterisk subsystem Module(s) Disp. Rationale
RTP / RTCP engine rtp_engine.c, res_rtp_asterisk 🦀 Core Crown jewel. Built on str0m/webrtc-rs, sans-IO, dedicated timing (not the shared tokio pool).
SRTP / DTLS res_srtp 🦀 Core Mandatory-by-default encryption.
Bridging framework main/bridge.c 🦀 Core The 2-party + N-party call topology engine.
↳ softmix conference mixer bridges/bridge_softmix 🦀 Core N-party mixing on the hot path.
↳ simple / native-RTP bridge bridge_simple, bridge_native_rtp 🦀 Core Direct media flow (re-INVITE peers together) when no mixing needed.
↳ holding bridge bridge_holding 🦀 Core Parking/hold media state.
↳ native DAHDI bridge bridge_native_dahdi Drop Hardware.
DSP (DTMF/MF detect, silence, tone) main/dsp.c 🦀 Core Media-plane primitive; exposed to plugins as events.
Echo cancellation (hardware / sw) 🔌 Edge/FFI No mature pure-Rust EC; FFI speexdsp / WebRTC APM.
Audio hooks (tap/inject/volume) main/audiohook.c 🦀 Core Primitive behind recording, ChanSpy, whisper/barge.
Music on hold res_musiconhold 🦀 Core Just a media source; small, keep in core (asset from object storage).
Jitter buffer func_jitterbuffer, abstract jb 🦀 Core Quality-critical; part of the RTP path.

3. Call model & PBX core (main/)

Asterisk subsystem Module(s) Disp. Rationale
Channel abstraction main/channel.c (ast_channel) 🦀 Core The unifying "leg" object (signaling + media state). Reimagined as a Channel/leg.
PBX / dialplan engine main/pbx.c 🔁 Replace → declarative routing (data) + event-driven apps. No extensions.conf.
Call features (transfer/park/pickup) res_features, res_parking 🦀 Core Call-control primitives, exposed via the API.
Scheduler / taskprocessor / threadpool main/sched.c, taskprocessor.c, threadpool.c 🦀 Core tokio for control; dedicated timing threads + timer wheel for the 20ms media loop.
Internal message bus (Stasis) main/stasis*.c 🔁 Replace In-core dispatcher for intra-core; external event bus (NATS/Kafka) for cross-service.
Module loader main/loader.c 🔁 Replace → WASM host + service registry. No in-process .so.
ACL / named ACL main/acl.c, res_named_acl 🦀 Core Security primitive; policy-driven.
astdb (internal KV) main/db.c 🔁 Replace → state store (embedded KV or external).

4. Dialplan & programmability (pbx/, res_agi, res_ari, manager)

Asterisk subsystem Module(s) Disp. Rationale
extensions.conf dialplan pbx_config 🔁 Replace Declarative routing as data (GitOps) for the 90% case.
AEL / Lua dialplan pbx_ael, pbx_lua Drop Replaced by WASM call-flow + routing config.
Realtime dialplan pbx_realtime 🔁 Replace Routing in the config/state layer.
Call files / spool pbx_spool 🔁 Replace → originate via API.
Macro / Gosub / Stack app_stack, app_macro 🔁 Replace → WASM call-flow logic.
AGI / FastAGI res_agi 🔁 Replace → WASM plugins + gRPC. Same "external program controls a call," sandboxed and typed.
ARI (REST + stasis apps) res_ari*, res_stasis* 🦀 Core Becomes THE API — the control-plane resource model. api-docs/*.json is the reusable spec.
AMI (manager) main/manager*.c 🔁 Replace Drop the plaintext TCP protocol → mTLS gRPC + RBAC + audit log.
CLI main/cli.c 🔁 Replace Thin CLI that's an API client, not an in-process console.
DUNDi (distributed routing) pbx_dundi Drop Dead/niche.
ENUM lookup func_enum 🧩 Plugin Routing-time lookup; pluggable.

5. Applications (apps/)

Asterisk app Module Disp. Rationale
Dial / originate app_dial, app_originate 🦀 Core Core call origination, exposed via the API.
Queues / ACD app_queue ☁️ Service Stateful, independently scaled, business logic.
Voicemail app_voicemail ☁️ Service Stateful storage + business logic; isolate it.
Conferencing app_confbridge 🦀 Core + ☁️ Mixing in core (softmix); orchestration/features as a service.
MeetMe app_meetme Drop DAHDI-timing legacy; ConfBridge supersedes.
Recording app_mixmonitor, app_monitor 🦀 Core + ☁️ Audiohook tap in core; storage/lifecycle as a recording service.
ChanSpy / whisper / barge app_chanspy 🦀 Core Audiohook primitive; gated by RBAC.
Answering-machine detect app_amd 🦀 Core + 🧩 DSP primitive in core; policy/thresholds as a plugin.
IVR primitives (playback/read/say) app_playback, app_read, app_sayunixtime, app_directory 🦀 Core Media primitives (play/collect-digits/say) exposed to WASM apps.
Echo / test app_echo, app_milliwatt 🦀 Core Trivial diagnostics.
FollowMe app_followme 🧩 Plugin Routing policy → WASM/service.
Speech recognition app_speech_utils, res_speech ☁️ Service Modern ASR is external; expose as a service integration.
Fax res_fax, res_fax_spandsp (T.38) ☁️ Service (FFI) Niche, isolate fully; spandsp via FFI. Optional.

6. Telephony features / subscriptions

Asterisk subsystem Module(s) Disp. Rationale
Presence / BLF res_pjsip_pubsub, res_pjsip_exten_state 🦀 Core + ☁️ Signaling in core; state aggregation service.
MWI (message-waiting) res_mwi_external, app_voicemail MWI ☁️ Service Tied to the voicemail service.
STIR/SHAKEN (attestation) res_stir_shaken ☁️ Service Keep — modern, anti-robocall/fraud. Core to the security story.
Call recording lifecycle app_mixmonitor + storage ☁️ Service Object-storage backed, retention policy, tenant-scoped.
CDR (call detail records) cdr/, cdr_* ☁️ Service Event stream → billing/analytics pipeline.
CEL (channel event logging) cel/, cel_* ☁️ Service Folded into the unified event bus + analytics.

7. Codecs & formats (codecs/, formats/)

Codec / format Module Disp. Rationale
G.711 a-law / µ-law codec_alaw, codec_ulaw, codec_a_mu 🦀 Core Trivial, ubiquitous. Native Rust.
G.722 (HD) codec_g722 🦀 Core Common wideband. Native Rust.
Opus (external codec_opus) 🦀 Core (FFI) WebRTC default; libopus via FFI.
Sample-rate resample codec_resample 🦀 Core Needed for any transcoding/mixing.
GSM codec_gsm 🧩 Plugin (FFI) Optional; FFI libgsm.
G.726 / G.729 codec_g726 / (external) 🧩 Plugin (FFI) Optional; G.729 now patent-free.
Speex / iLBC / LPC10 / ADPCM / codec2 codec_speex, codec_ilbc, … Drop Obsolete; drop or community plugin.
Hardware transcode codec_dahdi Drop Hardware.
File formats (wav/sln/ogg-opus) format_wav, format_sln, … 🦀 Core Small set for prompts/recordings in core; rest as plugins.
Legacy file formats (gsm/g729/g726 files) format_* 🧩 Plugin Optional.

8. Config, state & data (res_config_*, res_sorcery*)

Asterisk subsystem Module(s) Disp. Rationale
.conf files main/config.c 🔁 Replace Config-as-data (declarative, validated, GitOps).
Sorcery (object config abstraction) res_sorcery* 🔁 Replace Typed config + state-store layer.
Realtime backends (ODBC/PgSQL/SQLite/cURL/LDAP) res_config_* 🔁 Replace One config/state service with pluggable backends.
HTTP media cache res_http_media_cache 🦀 Core Prompt/asset fetch → object storage + cache.
Security event logging res_security_log 🦀 Core Structured audit log; security-critical.

9. Management & APIs

Asterisk surface Module(s) Disp. Rationale
ARI (REST resource model + event WS) res_ari* 🦀 Core The control-plane API. Reuse the resource model.
AMI (plaintext TCP admin) manager*.c 🔁 Replace → mTLS gRPC + RBAC + audit. No plaintext admin channel.
HTTP server res_http* 🦀 Core TLS-only; axum/hyper.
CLI cli.c 🔁 Replace API-client CLI.

10. New subsystems (no Asterisk equivalent)

These are the "modern" deltas — load-bearing, not optional polish.

Subsystem Disp. Purpose
WASM plugin host 🦀 Core Sandboxed runtime (wasmtime) for extensions. The isolation boundary.
Multi-tenancy / isolation 🦀 Core Per-tenant keys, quotas, media separation, RBAC.
Toll-fraud / anomaly engine ☁️ Service Deny-by-default routing, spend caps, rate limits, pattern detection. The #1 real-world Asterisk wound.
Secrets / KMS 🦀 Core Vault/KMS integration; no plaintext credentials in config.
Observability (OTel) 🦀 Core Distributed traces that follow a single call across services.
Event bus ☁️ Service NATS/Kafka/Redis Streams; replaces cross-service Stasis.
SBOM / supply chain cargo-deny, SBOM generation, reproducible builds.
Fuzzing harness Continuous fuzzing of every wire parser (SIP/SDP/RTP).

Phasing (maps to the roadmap)

  • Phase 0 — Media core. RTP/SRTP endpoint, G.711+Opus, 2-party bridge, record+playback. (§2, §7). The riskiest/most valuable proof.
  • Phase 1 — Control plane + API. ARI-modeled REST/gRPC + event stream, auth, event bus, multi-tenancy. (§4, §9, §10).
  • Phase 2 — Signaling. WebRTC ingress first, then SIP via FFI/SBC. (§1).
  • Phase 3 — Conferencing + DSP + IVR primitives. Softmix, transcoding matrix, DTMF/IVR. (§2, §5).
  • Phase 4 — Feature services. Registrar, presence/MWI, queues, voicemail, recording, CDR, STIR/SHAKEN — as services/plugins. (§5, §6).

Open decisions

  • SIP: FFI pjproject vs. SBC front vs. pure-Rust. Decided — ADR-0001: native Rust core behind a Kamailio + rtpengine edge.
  • WASM runtime: wasmtime component model (presumptive).
  • Event bus: NATS vs. Kafka vs. Redis Streams.
  • License (see README).