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 owns the wire-level concerns — SIP
5//! registration, dialogs, SDP offer/answer, and RTP framing — on a from-scratch
6//! engine (no external SIP stack), while staying out of audio device I/O, codec
7//! work, and call orchestration so it remains light and embeddable.
8//!
9//! The SIP transaction/dialog/transport engine is built in-house (see the
10//! `stack` plan in `docs/08-own-sip-stack.md`); only the [`rsip`] crate is used,
11//! for SIP message types. The engine is an internal detail — consumers depend on
12//! `wavekat-sip` alone.
13//!
14//! [`rsip`]: https://crates.io/crates/rsip
15//!
16//! # Scope
17//!
18//! What this crate covers:
19//!
20//! - **SIP signaling** — REGISTER with digest auth and keepalive
21//!   re-registration ([`Registrar`]), a bound endpoint with inbound-call
22//!   routing ([`SipEndpoint`]), outbound calls ([`Caller`]), and inbound calls
23//!   ([`IncomingCall`]).
24//! - **SDP** — offer/answer for Opus (RFC 7587, preferred) and G.711
25//!   (PCMU + PCMA) with round-trip parsing ([`build_sdp_offer`],
26//!   [`parse_sdp`]); answers and mid-call re-offers pin the negotiated
27//!   codec ([`CodecMenu`]). Negotiation only — encode/decode stays with
28//!   the consumer.
29//! - **RTP** — header parser ([`RtpHeader`]), a debug-friendly receive loop
30//!   ([`receive_rtp`]), and a codec-agnostic send loop ([`send_loop`]).
31//!
32//! Explicitly out of scope (push these to the consuming application): audio
33//! device I/O, codec encode/decode, jitter buffering, recording; account
34//! persistence; call orchestration / AI pipeline / business logic.
35//!
36//! # Quick start: register against a SIP server
37//!
38//! ```no_run
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 = SipEndpoint::new(&account, cancel.clone()).await?;
56//!
57//! // Expires: 60s, re-register every 50s.
58//! let registrar = Registrar::new(account, endpoint, cancel, 60, 50)?;
59//! registrar.register().await?;
60//! registrar.keepalive_loop().await;
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! # Placing an outbound call
66//!
67//! ```no_run
68//! use std::sync::Arc;
69//! use wavekat_sip::{Caller, SipAccount, SipEndpoint};
70//!
71//! # async fn run(account: SipAccount, endpoint: Arc<SipEndpoint>)
72//! #     -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
73//! let caller = Caller::new(account, endpoint);
74//! let target: wavekat_sip::re_exports::Uri = "sip:bob@example.com".try_into()?;
75//! let mut call = caller.dial(target).await?;
76//!
77//! // Wire RTP to your audio / AI pipeline using call.rtp_socket and
78//! // call.remote_media. Hang up locally with:
79//! call.hangup().await?;
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! # Answering inbound calls
85//!
86//! ```no_run
87//! use std::sync::Arc;
88//! use wavekat_sip::SipEndpoint;
89//!
90//! # async fn run(endpoint: Arc<SipEndpoint>)
91//! #     -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
92//! while let Some(incoming) = endpoint.next_incoming_call().await {
93//!     // Inspect incoming.remote_media, then accept (or reject):
94//!     let _call = incoming.accept().await?;
95//! }
96//! # Ok(())
97//! # }
98//! ```
99//!
100//! # Building SDP and parsing the answer
101//!
102//! ```
103//! use std::net::{IpAddr, Ipv4Addr};
104//! use wavekat_sip::{build_sdp_offer, parse_sdp, AudioCodec};
105//!
106//! let local_ip: IpAddr = Ipv4Addr::new(192, 168, 1, 50).into();
107//! let offer = build_sdp_offer(local_ip, 20000); // Opus preferred, G.711 fallback
108//!
109//! let answer = offer.clone(); // simulate a loopback answer
110//! let media = parse_sdp(&answer).expect("valid SDP");
111//! assert_eq!(media.port, 20000);
112//! assert_eq!(media.codec, Some(AudioCodec::Opus { payload_type: 111 }));
113//! ```
114//!
115//! # Reading RTP headers off the wire
116//!
117//! ```
118//! use wavekat_sip::RtpHeader;
119//!
120//! let packet = [
121//!     0x80, 0x00, 0x04, 0xD2, // V=2, PT=0 (PCMU), seq=1234
122//!     0x00, 0x00, 0x16, 0x2E, // timestamp
123//!     0xDE, 0xAD, 0xBE, 0xEF, // SSRC
124//! ];
125//! let header = RtpHeader::parse(&packet).unwrap();
126//! assert_eq!(header.payload_type, 0);
127//! assert_eq!(header.sequence, 1234);
128//! assert_eq!(header.header_len(), 12);
129//! ```
130//!
131//! # Module map
132//!
133//! | Module          | Purpose                                                        |
134//! |-----------------|----------------------------------------------------------------|
135//! | [`account`]     | Runtime [`SipAccount`] + [`Transport`] enum.                   |
136//! | [`endpoint`]    | [`SipEndpoint`] — bound transport, engine, inbound-call routing.|
137//! | [`registrar`]   | REGISTER + digest auth + keepalive re-registration.            |
138//! | [`resolve`]     | RFC 3263 (subset) server location: SRV + A/AAAA fallback.      |
139//! | [`callee`]      | [`IncomingCall`] — inbound INVITE accept/reject.               |
140//! | [`caller`]      | [`Caller`] outbound dial + the [`Call`] handle.                |
141//! | [`sdp`]         | Opus + G.711 offer/answer build + parse (negotiation only).    |
142//! | [`rtp`]         | RTP header parser, debug receive loop, codec-agnostic send loop. |
143//!
144//! # Stability
145//!
146//! Pre-1.0. The public API may still shift between minor versions.
147//!
148//! # License
149//!
150//! Licensed under Apache 2.0. Copyright 2026 WaveKat.
151
152#![cfg_attr(docsrs, feature(doc_cfg))]
153
154pub mod account;
155pub mod callee;
156pub mod caller;
157pub mod dtmf_info;
158pub mod endpoint;
159pub mod inbound;
160pub mod refer;
161pub mod registrar;
162pub mod resolve;
163pub mod rtp;
164pub mod sdp;
165pub mod session_timer;
166// Internal clean-room SIP engine (see `docs/08-own-sip-stack.md`). Entirely
167// `pub(crate)`: it never appears in this crate's public API.
168pub(crate) mod stack;
169
170pub use account::{SipAccount, Transport};
171pub use callee::IncomingCall;
172pub use caller::{Call, CallSession, Caller, InboundRequests};
173pub use dtmf_info::{build_info_body, content_type_header, InfoOutcome};
174pub use endpoint::SipEndpoint;
175pub use inbound::InboundRequest;
176pub use refer::{
177    is_final_sipfrag, parse_sipfrag_status, refer_to_header, refer_to_with_replaces, DialogTriplet,
178};
179pub use registrar::{Registrar, RegistrarDiagnostics};
180pub use resolve::{order_candidates, resolve_sip_server, SrvRecord};
181pub use rtp::dtmf::{
182    build_event_payload, build_rtp_dtmf_packet, send_dtmf_burst, DtmfBurstConfig, DtmfDigit,
183    DEFAULT_VOLUME_DBM0,
184};
185pub use rtp::dtmf_recv::{parse_event_payload, DtmfEvent, DtmfEventPayload, DtmfReceiver};
186pub use rtp::{receive_rtp, send_loop, RtpHeader, RtpSendConfig};
187pub use sdp::{
188    build_sdp_offer, build_sdp_with, parse_sdp, select_codec, select_dtmf, AudioCodec, CodecMenu,
189    DtmfSpec, MediaDirection, RemoteMedia, DTMF_DEFAULT_PT, DTMF_WIDEBAND_DEFAULT_PT,
190    OPUS_DEFAULT_PT, OPUS_RTP_CLOCK_RATE,
191};
192pub use session_timer::{
193    min_se_in, negotiate_uac, negotiate_uas, require_timer_header, session_expires_in,
194    session_timer_loop, supported_timer_header, supports_timer, Refresher, SessionDialogOps,
195    SessionExpires, SessionTimer, SessionTimerOutcome, UasSessionTimer,
196    DEFAULT_SESSION_EXPIRES_SECS, MIN_SESSION_EXPIRES_SECS,
197};
198
199/// Re-exports of the [`rsip`] message types that appear in our public API.
200/// Pinning them here lets consumers depend only on `wavekat-sip`.
201pub mod re_exports {
202    pub use rsip::{Header, Headers, Method, StatusCode, Uri};
203}
204
205/// Short git hash this crate was built from, or `"unknown"` if unavailable.
206pub const GIT_HASH: &str = env!("WAVEKAT_SIP_GIT_HASH");