SIP client stack · softphones · voice agents
SIP in Rust.
One C ABI.
No GPL code inside.
sipral Session Initiation Protocol Rust Audio Layer
A library that makes and answers phone calls over the internet: calls, audio, encryption and getting through home and office routers. Use it in place of PJSIP without the GPL, or plug a phone line straight into a voice agent.
- concurrent calls in one process
- 10 000
- Audio both ways on every call, set up at about 500 a second.
- of memory per call
- 72 KB
- A softphone barely notices it; a server holds thousands of calls.
- fuzzing executions
- 61 billion
- Hostile input thrown at the parsers and protocol engines. Zero crashes.
- languages, one interface
- 8
- Swift, Kotlin, Java, .NET, Python, Dart, React Native and Node.js, over one C ABI.
01 The API
A call, in Python.
No bind address, no media address: the stack listens on every interface and advertises the route toward the server it found by RFC 3263.
With audio=AudioMode.APPLICATION the same call
hands its audio over instead: call.media.frames
yields the far end as 16-bit PCM, and send_audio(pcm)
is what the far end hears. That is all a voice agent needs.
Swift, Kotlin, .NET, Dart, React Native and Node.js have the same shape.
import asyncio
from sipral import Stack
from sipral.enums import RegistrationState
async def main():
with Stack(loop=asyncio.get_running_loop()) as stack: # device mode: the library opens mic and speaker
account = stack.add_account(
"sip:alice@example.com", registrar="sip:example.com",
server_uri="sip:example.com", # located by RFC 3263: NAPTR, SRV, A/AAAA
auth_user="alice", auth_password="secret")
account.register()
while account.registration_state != RegistrationState.REGISTERED:
await stack.events.get()
call = stack.place_call(account, "sip:bob@example.com")
while not call.ended:
print((await call.events.get()).kind_name)
asyncio.run(main()) 02 Why teams choose it
What it changes for your product.
- 01
Fewer crashes, fewer CVEs
Written in Rust, so whole classes of memory bugs cannot happen. The few unsafe lines sit only where it talks to C and to the sound card, and code that could crash at runtime fails the build.
61 billion fuzzing executions, zero crashes
- 02
Closed source and app stores
No GPL or LGPL code anywhere in it: written clean-room from the RFCs, with permissively licensed dependencies only. Under the commercial licence it ships in closed products and through the app stores.
AGPL-3.0 or a commercial licence
- 03
One integration, every platform
One C ABI, with the Swift, Kotlin, .NET, Python, Dart and Node.js bindings generated from it, plus Java, .NET MAUI and React Native on top. iOS, Android, macOS, Windows and Linux from one codebase.
layouts verified on 64-bit and both 32-bit ABIs
- 04
Voice agents, no media server
Each call hands what the caller says to your agent as plain audio, 20 ms at a time, and plays back whatever the agent says. The agent can live in the same program or in a separate one, or run entirely on your machine with no key.
application mode on every platform and language
- 05
A softphone with no audio code
Device mode opens the microphone and speaker itself, with the platform echo cancellation: CoreAudio on Apple platforms, WASAPI on Windows, AAudio on Android.
CallKit, PushKit and ConnectionService helpers
- 06
Encrypted the way carriers ask
SRTP with SDES and AES-GCM, DTLS-SRTP, TLS with the platform trust store, a private CA or a pinned certificate, and STIR/SHAKEN to sign calls and verify callers.
SRTP best effort for PBXs that refuse encrypted offers
- 07
Calls that survive real networks
STUN with failover, TURN over UDP, TCP and TLS, ICE in the full and lite roles, keep-alives that hold NAT bindings open, and a call that follows its own address when the phone changes networks.
RFC 8489 · 8656 · 8445 · 5626
- 08
The PBXs your customers run
Tested against Asterisk, FreeSWITCH, FusionPBX, Kamailio, OpenSIPS, baresip, FreePBX 16 and 17, VitalPBX and a VICIdial dialer, on good and deliberately bad networks.
calls, transfers, DTMF, SRTP, DTLS-SRTP, ICE, TURN
- 09
Measured, not claimed
Ten thousand concurrent calls in one process at about 72 KB each. The least memory and the least CPU per call, measured against PJSIP, baresip and Linphone.
The measured comparison
03 Architecture
The two open cells are the core.
The heart of the stack never touches the network or the clock. Your code hands it bytes and the time; it hands back bytes to send and events to act on. That makes every case repeatable in a test.
In the mark, seven filled cells trace the S. The two left open are the part of the stack that opens no sockets. The red one is the active call.
fn receive(&mut self, input: Input<'_>, now: Instant) -> Result<(), ReceiveError>;
fn handle_timeout(&mut self, now: Instant);
fn poll_transmit(&mut self) -> Option<Transmit>;
fn poll_event(&mut self) -> Option<Event>;
fn poll_timeout(&self) -> Option<Instant>; - i.
Fewer bugs in the field.
Every state machine is tested without a network: a CANCEL racing a 200 OK is reproduced in a unit test, not on a customer's carrier.
- ii.
It fits your architecture.
It embeds in Swift concurrency, a .NET Task, Kotlin coroutines, Tokio or a bare event loop.
- iii.
It never blocks your app.
There is no socket inside the core to block on.
- iv.
It ships lean.
Audio devices and TLS stay at the edges, and a headless build does not link them.
04 On the wire
Every call, verified message by message.
The load test runs this exchange for a thousand calls at once, and fails if a single message goes missing or a single call ends the wrong way.
- RFC 3263
- NAPTR, SRV, A/AAAA; failover on timeout, lost transport or 503; re-lookup on TTL
- RFC 3261 §18.1.1
- a request over 1300 bytes is not put in a datagram: TCP, or a trimmed retry
- RFC 3262 · 3311
- reliable provisional responses with PRACK, and UPDATE
- RFC 4028
- session timers
- RFC 5626 · 5627
- Outbound keep-alives and GRUU
- RFC 8599
- calls announced by push notification
- INVITE SDP offer
- 401 Unauthorized digest challenge
- ACK
- INVITE with credentials
- 100 Trying
- 180 Ringing reliable, RFC 3262
- PRACK
- 200 OK PRACK
- 200 OK SDP answer
- ACK
- RTP or SRTP, audio both ways
- re-INVITE hold, then resume
- BYE
- 200 OK
05 Interoperability
Proven against the PBXs your customers run.
| Peer | Register | Call | Hold, resume | Blind transfer | Attended transfer | DTMF, RFC 4733 | DTMF, SIP INFO | SRTP, SDES | DTLS-SRTP | Compact form | G.729 | MESSAGE | MWI |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Asterisk 22.10.1 the configuration most integrators run | |||||||||||||
| FreeSWITCH via Kamailio 1.10.12 / 6.1.4 registration and routing through a proxy | |||||||||||||
| FreeSWITCH via OpenSIPS 1.10.12 / 4.0.2 a second, independent proxy | |||||||||||||
| baresip via Kamailio 4.11.0 / 6.1.4 phone to phone, end to end | |||||||||||||
| FreePBX 17 17 (Asterisk 22.10.1, Debian 12) its Asterisk on the lab's flows, beside its own extensions | |||||||||||||
| FreePBX 16 1 16 (Asterisk 18.9-cert13) its Asterisk on the lab's flows, beside its own extensions | |||||||||||||
| FusionPBX 5.6.5 (FreeSWITCH 1.10.12) its own database, profiles and dialplan | |||||||||||||
| VitalPBX 2 4.5 (Asterisk 20) a running system, on two test tenants | |||||||||||||
| VICIdial ViciBox 12 (Asterisk 18.26) its Asterisk on the lab's flows, and an agent's phone rung into its session |
- 1 This FreePBX 16 runs on a system with OpenSSL 1.0.2, which predates the extended master secret (RFC 7627). Sipral does not key DTLS-SRTP without it, as RFC 7627 §5.4 asks; on FreePBX 17, with OpenSSL 3, it passes.
- 2 VitalPBX ties each mailbox to the extension, refuses a subscription to it and sends message waiting unasked; Sipral answers a NOTIFY outside a subscription 481 (RFC 6665 §4.1.3), so MWI does not pass there yet. The test tenants offer PCMA alone and no media encryption, so SRTP, DTLS-SRTP and G.729 are not tried.
- Asterisk 22.10.1, from behind a NAT
- calling and called through STUN, and still reachable minutes after registering: keep-alives hold the NAT open
- Sipral to Sipral, each behind its own NAT
- full ICE through two NATs, relayed through TURN where the NATs block a direct path, forked calls relayed to two phones, STUN failover past a dead server
- Headless agent, ICE-lite
- ICE required by the far end, through the native API and the C interface
- Two Sipral stacks, C ABI to C ABI
- STIR/SHAKEN: signed calls verified and carried, unsigned calls and untrusted signatures refused
61 billion
fuzzing executions
Malformed, hostile and random input against the parsers and protocol engines. Zero crashes, zero hangs.
RFC 4475
the SIP torture suite
Every message of the IETF's torture tests parsed and asserted.
Rust
memory-safe throughout
Unsafe code confined to the C boundary and the audio device layer.
06 Compared with PJSIP, baresip and Linphone
Less memory. Less CPU. An INVITE that always gets through.
Four SIP clients against the same Asterisk, each the build its distribution ships: timed from packet captures, measured from the same /proc files.
5.8 MB registered and idle, where the others need 12 to 58 MB. A fifth of baresip's CPU per call at a hundred calls, a sixth of pjsua's over three hours of calls. The only authenticated ICE INVITE that crosses UDP whole, and audio back in well under a second after the network changes.
Every measured table-
Memory, registered and idle
Sipral5.8 MB pjsua12.7 MB baresip12.2 MB linphonec57.7 MB -
Processor time per call, three hours of three-minute calls
Sipral1.77 s pjsua10.46 s baresip4.08 s linphonec2.84 s -
The INVITE that sets up an ICE call
Sipral1236 B pjsua1869 B fragmented baresip1479 B fragmented, one run of two linphonec1594 B fragmented -
Audio back after the network changed
Sipral640 ms pjsuanever baresip2200 ms linphonec2100 ms
07 One C ABI
Five platforms, one header, every binding printed from it.
#define SIPRAL_ABI_VERSION_MAJOR ((uint32_t)1)
#define SIPRAL_ABI_VERSION_MINOR ((uint32_t)2)
typedef uint64_t sipral_handle_t;
sipral_status_t sipral_stack_create(const sipral_stack_config_t *config, sipral_handle_t *out_stack);
sipral_status_t sipral_stack_poll(sipral_handle_t stack, uint64_t now_ms, sipral_poll_result_t *result);
sipral_status_t sipral_account_add(sipral_handle_t stack, const sipral_account_config_t *config, sipral_handle_t *out_account);
sipral_status_t sipral_account_register(sipral_handle_t stack, sipral_handle_t account, uint64_t now_ms);
sipral_status_t sipral_call_place(sipral_handle_t stack, sipral_handle_t account, const sipral_call_config_t *config, sipral_handle_t *out_call, uint64_t now_ms);
sipral_status_t sipral_media_receive(sipral_handle_t media, uint8_t *data, size_t len, const char *from, size_t from_len, uint64_t now_ms, sipral_arrival_t *out_arrival);
sipral_status_t sipral_media_playback(sipral_handle_t media, int16_t *samples, size_t capacity, size_t *out_written, sipral_playback_t *out_source);
sipral_status_t sipral_call_hangup(sipral_handle_t stack, sipral_handle_t call, uint64_t now_ms); | Platform | Languages |
|---|---|
| macOS | RustCSwiftKotlin/Java.NETPythonDartNode.js |
| iOS | RustCSwift.NET MAUIReact NativeFlutter |
| Android | RustCKotlin.NET MAUIReact NativeFlutter |
| Windows | RustC.NETPythonDartNode.js |
| Linux | RustCKotlin/Java.NETPythonDartNode.js |
Application mode, PCM in the application's hands, is available on every platform and in every language.
Platforms, artefacts and binding examples08 Licence
Pick the arm that fits what you ship.
There is no licence check inside the library. The commercial arm is a contract, not a runtime lock.
AGPL-3.0-only
Free
- Open source apps that are themselves copyleft
- Research, teaching, evaluation, prototypes you do not ship
- Network use of a modified Sipral, with your source published
LicenseRef-Sipral-Commercial
Commercial
- Closed-source desktop, mobile and server products
- App Store and Google Play distribution
- One price per company, no royalties, no per-seat or per-channel counting
09 Roadmap
1.2 is out. Here is what comes next.
-
October 2026 · 1.2 (released)
Voice agents, the enterprise and more platforms
Connectors for the services voice agents run on, OAuth2 sign-in to the PBX, Node.js and .NET MAUI packages, a network test before the call, answering-machine detection, SIP over WebSocket and LiveCommunicationKit on iOS.
-
November 2026 · 1.3 In progress (planned, not done)
On real phones, and on WebRTC lines
The iOS and Android packages proven on real phones and through the test tracks of both stores, and calls on the WebRTC lines PBXs offer: one port for the whole call, in the profile browsers speak.
-
December 2026 · 1.4 In progress (planned, not done)
Browsers and the telephone network
Trickle ICE, an audio call to and from a browser, SIP-T and SIP-I through a carrier gateway, and C, C++ and Rust applications that no longer write their own networking.
-
March 2027 · 1.5 (planned, not done)
Keys that never leave the device
A client certificate on SIP over TLS, the private key held and used by the platform security hardware, on phones and on the desktop alike.
What is Sipral?
Sipral (Session Initiation Protocol Rust Audio Layer) is a SIP client stack written in Rust: signalling, media, encryption and NAT traversal behind one C ABI. It is meant to replace PJSIP in softphones and to put a phone line inside a voice agent without a media server in between.
Can Sipral replace PJSIP in an existing softphone?
That is what it is built for. It covers registration, calls, hold, transfers, DTMF, SRTP, DTLS-SRTP, ICE and TURN, and its documentation includes a migration guide that maps each pjsua concept to its equivalent in Sipral. It contains no GPL or LGPL code, so a closed-source product does not inherit GPL obligations from it.
Does Sipral contain any GPL or LGPL code?
No. It was written clean-room from the RFCs and ITU Recommendations, and it links only permissively licensed dependencies (MIT, BSD, Apache-2.0, ISC, Zlib and equivalents). The build fails if a dependency under any other licence is added.
Which platforms and languages does Sipral support?
macOS, iOS, Android, Windows and Linux. Besides Rust and C, it has bindings for Swift, Kotlin and Java, .NET and .NET MAUI, Python, Dart and Flutter, React Native, and Node.js with TypeScript. The C header and the Swift, Kotlin, .NET, Python, Dart and Node.js bindings are generated from one declaration of the C ABI.
Can Sipral run without a microphone or speaker, for a voice agent?
Yes. Next to device mode, where the library opens the microphone and speaker itself, application mode hands every call's audio to the application as 16-bit PCM, one frame at a time, and takes what the far end should hear back the same way. A headless build can also carry that audio over a socket to a separate agent process.
Which encryption does Sipral support?
SRTP keyed by SDES (AES-CM 128 and 256, AES-GCM), DTLS-SRTP written in-tree, an SRTP best-effort mode for PBXs that refuse encrypted offers, TLS with the platform trust store, a private CA or one pinned certificate, and STIR/SHAKEN signing and verification (RFC 8224, RFC 8588).
How does Sipral get through NAT?
STUN with failover, TURN over UDP, TCP and TLS, ICE in the full and lite roles with restarts and consent freshness, RFC 5626 keep-alives, and a call that follows its own address when the network changes.
Which version is current?
Sipral 1.2, at C ABI 1.2. An application built against 1.0 or 1.1 keeps working with every later 1.x library, without a rebuild. The packages are on PyPI, npm, NuGet, Maven Central, pub.dev and crates.io, the Swift package is the GitHub repository, and commercial licences are available through the form.
Do I need a commercial licence to ship Sipral in an App Store app?
Yes. Under the AGPL-3.0 the whole application must reach its users with its source, which the terms of the App Store and Google Play conflict with. The commercial licence allows closed-source distribution and app stores explicitly, with no royalties and no per-seat or per-channel counting.
How is Sipral tested?
With 61 billion fuzzing executions against its parsers and protocol engines and zero crashes, with every message of the RFC 4475 SIP torture suite asserted, and with a lab that runs calls, transfers, DTMF, SRTP, DTLS-SRTP, NAT, ICE and TURN against Asterisk, FreeSWITCH, FusionPBX, Kamailio, OpenSIPS, baresip, FreePBX 16 and 17, VitalPBX and a VICIdial dialer, through the native API and again through the C interface.
Ship SIP in a closed app, without the GPL.
One price per company, perpetual for the versions delivered during maintenance, app stores allowed. Ask privately.
Request a quote