Skip to main content

wavekat_sip/
lib.rs

1//! SIP signaling and RTP transport for voice pipelines.
2//!
3//! `wavekat-sip` is a small, focused toolkit for building softphones, voice
4//! bots, and recording bridges in Rust. It wraps [`rsipstack`] and owns the
5//! wire-level concerns — SIP registration, dialogs, SDP offer/answer, and
6//! RTP framing — while staying out of audio device I/O, codec work, and
7//! call orchestration so it remains light and embeddable.
8//!
9//! [`rsipstack`]: https://crates.io/crates/rsipstack
10//!
11//! # Scope
12//!
13//! What this crate covers:
14//!
15//! - **SIP signaling** — REGISTER with digest auth and keepalive
16//!   re-registration ([`Registrar`]), shared transport + dialog layer
17//!   ([`SipEndpoint`]).
18//! - **SDP** — minimal G.711 (PCMU + PCMA) offer/answer with round-trip
19//!   parsing ([`build_sdp`], [`parse_sdp`]).
20//! - **RTP** — header parser ([`RtpHeader`]) and a debug-friendly receive
21//!   loop ([`receive_rtp`]) suitable for transcription, recording, or
22//!   smoke-testing inbound media.
23//!
24//! Explicitly out of scope (push these to the consuming application):
25//!
26//! - Audio device I/O (e.g. `cpal`), codec encode/decode, jitter buffering,
27//!   recording, WAV writing.
28//! - Account persistence (TOML files, system keychain).
29//! - Call orchestration, AI pipeline, business logic.
30//!
31//! Inbound INVITE handling lives in [`Callee`]; the outbound `caller`
32//! wrapper and an RTP send helper are still on the
33//! [roadmap](https://github.com/wavekat/wavekat-sip/blob/main/docs/01-port-plan.md).
34//!
35//! # Quick start: register against a SIP server
36//!
37//! ```no_run
38//! use std::sync::Arc;
39//! use tokio_util::sync::CancellationToken;
40//! use wavekat_sip::{Registrar, SipAccount, SipEndpoint, Transport};
41//!
42//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
43//! let account = SipAccount {
44//!     display_name: "Office".into(),
45//!     username: "1001".into(),
46//!     password: "secret".into(),
47//!     domain: "sip.example.com".into(),
48//!     auth_username: None,
49//!     server: None,
50//!     port: None,
51//!     transport: Transport::Udp,
52//! };
53//!
54//! let cancel = CancellationToken::new();
55//! let (endpoint, _incoming) = SipEndpoint::new(&account, cancel.clone()).await?;
56//! let endpoint = Arc::new(endpoint);
57//!
58//! // Expires: 60s, re-register every 50s.
59//! let registrar = Registrar::new(account, endpoint, cancel, 60, 50)?;
60//! registrar.register().await?;
61//! registrar.keepalive_loop().await;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! The `_incoming` stream returned by [`SipEndpoint::new`] yields inbound
67//! transactions (INVITE, OPTIONS, …). For INVITEs, hand the transaction to
68//! [`Callee::accept_transaction`] or [`Callee::reject_transaction`].
69//!
70//! # Building SDP and parsing the answer
71//!
72//! ```
73//! use std::net::{IpAddr, Ipv4Addr};
74//! use wavekat_sip::{build_sdp, parse_sdp};
75//!
76//! let local_ip: IpAddr = Ipv4Addr::new(192, 168, 1, 50).into();
77//! let offer = build_sdp(local_ip, 20000);
78//!
79//! // ... send `offer` in an INVITE, receive an SDP answer ...
80//! let answer = offer.clone(); // simulate a loopback answer
81//! let media = parse_sdp(&answer).expect("valid SDP");
82//! assert_eq!(media.port, 20000);
83//! assert_eq!(media.payload_type, 0); // PCMU
84//! ```
85//!
86//! # Reading RTP headers off the wire
87//!
88//! For real receive loops use [`receive_rtp`]; to inspect individual
89//! packets, parse directly:
90//!
91//! ```
92//! use wavekat_sip::RtpHeader;
93//!
94//! let packet = [
95//!     0x80, 0x00, 0x04, 0xD2, // V=2, PT=0 (PCMU), seq=1234
96//!     0x00, 0x00, 0x16, 0x2E, // timestamp
97//!     0xDE, 0xAD, 0xBE, 0xEF, // SSRC
98//! ];
99//! let header = RtpHeader::parse(&packet).unwrap();
100//! assert_eq!(header.payload_type, 0);
101//! assert_eq!(header.sequence, 1234);
102//! assert_eq!(header.header_len(), 12);
103//! ```
104//!
105//! # Module map
106//!
107//! | Module          | Purpose                                                        |
108//! |-----------------|----------------------------------------------------------------|
109//! | [`account`]     | Runtime [`SipAccount`] + [`Transport`] enum.                   |
110//! | [`endpoint`]    | [`SipEndpoint`] — bound transport, dialog layer, RX stream.    |
111//! | [`registrar`]   | REGISTER + digest auth + keepalive re-registration.            |
112//! | [`callee`]      | Inbound INVITE accept/reject helper.                           |
113//! | [`sdp`]         | Minimal G.711 offer/answer build + parse.                      |
114//! | [`rtp`]         | RTP header parser and async receive loop.                      |
115//!
116//! # Stability
117//!
118//! Pre-1.0. The public API will shift between minor versions while the
119//! `caller` helper lands. Pin an exact version if you need stability
120//! today.
121//!
122//! # License
123//!
124//! Licensed under Apache 2.0. Copyright 2026 WaveKat.
125
126#![cfg_attr(docsrs, feature(doc_cfg))]
127
128pub mod account;
129pub mod callee;
130pub mod endpoint;
131pub mod registrar;
132pub mod rtp;
133pub mod sdp;
134
135pub use account::{SipAccount, Transport};
136pub use callee::{AcceptedCall, Callee};
137pub use endpoint::SipEndpoint;
138pub use registrar::{Registrar, RegistrarDiagnostics};
139pub use rtp::{receive_rtp, RtpHeader};
140pub use sdp::{build_sdp, parse_sdp, RemoteMedia};
141
142/// Re-exports of upstream types that appear in our public API. Pinning
143/// them here lets consumers depend only on `wavekat-sip` without taking
144/// a direct dep on `rsip` / `rsipstack`.
145pub mod re_exports {
146    pub use rsip::{Header, Method, StatusCode};
147    pub use rsipstack::transaction::transaction::Transaction;
148}
149
150/// Short git hash this crate was built from, or `"unknown"` if unavailable.
151pub const GIT_HASH: &str = env!("WAVEKAT_SIP_GIT_HASH");