Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
30cb878
docs: plan from-scratch SIP stack
wavekat-eason Jun 27, 2026
077044d
feat: add RFC 3261 §17 transaction engine (sans-IO)
wavekat-eason Jun 27, 2026
a1622da
feat: add UDP transport + async transaction engine
wavekat-eason Jun 27, 2026
319579a
feat: add digest auth challenge/retry orchestration
wavekat-eason Jun 27, 2026
590eaea
feat: add RFC 3261 §12 dialog layer
wavekat-eason Jun 27, 2026
cca84af
feat: compose REGISTER-with-digest flow on the engine
wavekat-eason Jun 27, 2026
d75a2d9
feat: compose outbound INVITE call flow on the engine
wavekat-eason Jun 27, 2026
7365550
feat: add UA router so one engine serves many flows
wavekat-eason Jun 27, 2026
ce1a418
docs: add phase 7 UA router plan doc
wavekat-eason Jun 27, 2026
7ad4f55
feat: add UAS response builder to the stack
wavekat-eason Jun 27, 2026
d2cd9bc
feat!: drop rsipstack — run on the in-house engine
wavekat-eason Jun 27, 2026
96cd21e
test: end-to-end public call over loopback + doc the cutover
wavekat-eason Jun 27, 2026
1826a23
docs: delink internal Ua type in endpoint module doc
wavekat-eason Jun 27, 2026
491c00b
docs: plan reinstating deferred call features
wavekat-eason Jun 27, 2026
6611618
feat: in-dialog request seam + DTMF over SIP INFO
wavekat-eason Jun 27, 2026
098aa70
feat: hold/resume re-INVITE on the engine
wavekat-eason Jun 27, 2026
ede25b2
feat: emit a User-Agent header on outbound requests
wavekat-eason Jun 27, 2026
e32ff3a
feat: re-add RFC 4028 session timers on the engine
wavekat-eason Jun 27, 2026
79eb506
docs: audit RFC coverage after reinstating features
wavekat-eason Jun 27, 2026
8e90448
feat: surface inbound in-dialog re-INVITE / INFO to the Call
wavekat-eason Jun 27, 2026
acf3d45
feat: CANCEL a still-ringing outbound INVITE
wavekat-eason Jun 27, 2026
4736285
feat: observe provisional (180 Ringing) on outbound dial
wavekat-eason Jun 27, 2026
cafafd7
docs: mark Phase 6 done and re-audit RFC coverage
wavekat-eason Jun 27, 2026
ec02534
feat: surface remote BYE and inbound CANCEL on the engine
wavekat-eason Jun 28, 2026
a3461ee
test: cover CANCEL correlation and 487 edge cases
wavekat-eason Jun 28, 2026
6978553
fix: route in-dialog requests back through NAT proxies
wavekat-eason Jun 28, 2026
e84153e
feat: log raw SIP wire and unmatched BYE for diagnosis
wavekat-eason Jun 28, 2026
7196a67
docs: refresh README for the in-house engine and shipped call API
wavekat-eason Jun 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 56 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
[![Crates.io](https://img.shields.io/crates/v/wavekat-sip.svg)](https://crates.io/crates/wavekat-sip)
[![docs.rs](https://docs.rs/wavekat-sip/badge.svg)](https://docs.rs/wavekat-sip)

SIP signaling and RTP transport for [WaveKat](https://wavekat.com) voice pipelines, built on
[`rsipstack`](https://crates.io/crates/rsipstack). Same pattern as
SIP signaling and RTP transport for [WaveKat](https://wavekat.com) voice
pipelines, on a from-scratch SIP engine (no external SIP stack). Same pattern as
[wavekat-vad](https://github.com/wavekat/wavekat-vad) and
[wavekat-turn](https://github.com/wavekat/wavekat-turn).

Expand All @@ -20,11 +20,12 @@ SIP signaling and RTP transport for [WaveKat](https://wavekat.com) voice pipelin
A small, focused SIP/RTP toolkit for building softphones, voice bots, and
recording bridges in Rust. It owns the wire-level concerns —

- **SIP signaling**: REGISTER (with digest auth + keepalive), INVITE (in/out),
BYE, dialog tracking.
- **SDP**: minimal offer/answer for G.711 telephony audio.
- **RTP**: header parser and a receive loop suitable for transcription /
recording / debug.
- **SIP signaling**: REGISTER (digest auth + keepalive), outbound and inbound
calls (`Caller` / `IncomingCall`), in-dialog hold/resume, DTMF (RFC 4733 +
INFO fallback), and RFC 4028 session timers.
- **SDP**: minimal offer/answer for G.711 (PCMU + PCMA) telephony audio.
- **RTP**: header parser, a debug-friendly receive loop, and a codec-agnostic
send loop.

— and stays out of the audio device, codec, and call-orchestration layers
so it remains light and embeddable.
Expand All @@ -38,7 +39,6 @@ cargo add wavekat-sip
Register an account against your SIP server:

```rust,no_run
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use wavekat_sip::{Registrar, SipAccount, SipEndpoint, Transport};

Expand All @@ -55,8 +55,7 @@ let account = SipAccount {
};

let cancel = CancellationToken::new();
let (endpoint, _incoming) = SipEndpoint::new(&account, cancel.clone()).await?;
let endpoint = Arc::new(endpoint);
let endpoint = SipEndpoint::new(&account, cancel.clone()).await?;

// Expires: 60s, re-register every 50s.
let registrar = Registrar::new(account, endpoint, cancel, 60, 50)?;
Expand All @@ -66,34 +65,67 @@ registrar.keepalive_loop().await;
# }
```

INVITE wrappers (`Caller`, `Callee`) land in the next release. Until then,
drive `SipEndpoint::dialog_layer` directly.
Place an outbound call and hang up:

```rust,no_run
use std::sync::Arc;
use wavekat_sip::{Caller, SipAccount, SipEndpoint};

# async fn run(account: SipAccount, endpoint: Arc<SipEndpoint>)
# -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let caller = Caller::new(account, endpoint);
let target: wavekat_sip::re_exports::Uri = "sip:bob@example.com".try_into()?;
let mut call = caller.dial(target).await?;

// Wire call.rtp_socket + call.remote_media to your audio / AI pipeline, then:
call.hangup().await?;
# Ok(())
# }
```

Answer inbound calls from the endpoint's incoming stream:

```rust,no_run
# use std::sync::Arc;
# use wavekat_sip::SipEndpoint;
# async fn run(endpoint: Arc<SipEndpoint>)
# -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
while let Some(incoming) = endpoint.next_incoming_call().await {
// Inspect incoming.remote_media, then accept (or reject):
let _call = incoming.accept().await?;
}
# Ok(())
# }
```

## Status

| Module | State |
|-------------|------------------------------------------------------|
| `account` | Stable — runtime SIP account type. |
| `endpoint` | Working — shared SIP endpoint + transport. |
| `registrar` | Working — REGISTER + auth + keepalive + unregister. |
| `sdp` | Working — minimal G.711 offer/answer. |
| `rtp` | Header parser + debug receive loop. RTP send next. |
| `caller` | _Planned_ — outbound INVITE wrapper. |
| `callee` | _Planned_ — inbound INVITE accept/reject helper. |
| Module | State |
|-------------|--------------------------------------------------------|
| `account` | Stable — runtime SIP account type. |
| `endpoint` | Working — shared SIP endpoint + transport + routing. |
| `registrar` | Working — REGISTER + auth + keepalive + unregister. |
| `resolve` | Working — RFC 3263 (subset) SRV + A/AAAA fallback. |
| `caller` | Working — outbound dial, hold/resume, DTMF, hangup. |
| `callee` | Working — inbound INVITE accept/reject. |
| `sdp` | Working — minimal G.711 offer/answer. |
| `rtp` | Working — header parser, receive loop, send loop. |

## Architecture

```
PSTN / SIP trunk
wavekat-sip ──► rsipstack (transport, transactions, dialogs)
wavekat-sip (in-house transport, transactions, dialogs)
├─ account ──── credentials + endpoint config
├─ endpoint ─── UDP/TCP transport + DialogLayer
├─ endpoint ─── UDP transport + transaction/dialog engine + routing
├─ registrar ── REGISTER / digest auth / keepalive
├─ caller ───── outbound INVITE / hold / DTMF / hangup
├─ callee ───── inbound INVITE accept / reject
├─ sdp ──────── offer/answer for telephony codecs
└─ rtp ──────── RTP header parse + receive
└─ rtp ──────── RTP header parse / receive / send
your app ──► audio device I/O, codec, recording, AI pipeline
Expand Down Expand Up @@ -122,6 +154,4 @@ Copyright 2026 WaveKat.

### Acknowledgements

- [`rsipstack`](https://crates.io/crates/rsipstack) — the SIP transaction /
dialog engine this crate wraps.
- [`rsip`](https://crates.io/crates/rsip) — SIP message types.
6 changes: 2 additions & 4 deletions crates/wavekat-sip/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ categories = ["network-programming", "multimedia::audio"]
build = "build.rs"

[dependencies]
# Only `rsip` (SIP message types) is used; the transaction/dialog/transport
# engine is built in-house under `src/stack/` (see docs/08-own-sip-stack.md).
rsip = "0.4"
rsipstack = "0.4"
tokio = { version = "1", features = ["macros", "rt", "net", "sync", "time"] }
tokio-util = { version = "0.7", features = ["rt"] }
tracing = "0.1"
serde = { version = "1", features = ["derive"] }
thiserror = "2"
hostname = "0.4"
# DNS SRV lookups for RFC 3263 server location (src/resolve.rs).
# Already in our tree via rsipstack's default `srv_lookup` feature
# (same version, same default features), so this adds no new
# transitive deps.
hickory-resolver = "0.25"

[dev-dependencies]
Expand Down
Loading