Library API - NormB/sipnab GitHub Wiki
sipnab is primarily a CLI/TUI tool. Its analysis engine is also a Rust library on crates.io.
- The curated public API is re-exported at the crate root.
- Anything under a
#[doc(hidden)]module (cli,tui,privilege, โฆ) is binary-internal.
The library API is not stable.
- The supported way to use sipnab is the program,
cargo install sipnab. - sipnab releases often.
- Any release, including a patch release, can rename, move or remove a public item.
That matters because of how cargo reads a version.
- A requirement of
"0.5"means "any 0.5.x release". - So
cargo updatecan move your build to a release that no longer compiles against your code. - Pin the exact release you built against, with
=:
[dependencies]
# Replace N with the patch number of the release you tested.
sipnab = { version = "=0.5.N", default-features = false, features = ["native"] }Then upgrade on purpose:
- change the pin,
- rebuild,
- read the changelog for what moved.
-
sipnab::bytesandsipnab::chronore-export the buffer and timestamp types used in public signatures. - An application can import
sipnab::{bytes::Bytes, chrono::Utc}without adding matching dependency versions. - After parsing,
SipMessage::parse_errorreports whether the message was only partially parseable. This includes a truncated declared body.
| Item | What it is |
|---|---|
PcapReader |
Pure-Rust pcap/pcapng reader (iterator of packets) |
decompress_capture |
Transparent, bounded gunzip for gzip-compressed captures |
sip::parser::parse_sip / parse_sip_bytes
|
Parse raw bytes โ SipMessage
|
capture::parse::parse_packet |
Decode a captured Packet โ ParsedPacket
|
rtp::parser::parse_rtp_header |
Parse an RTP header |
sip::sdp::parse_sdp |
Parse an SDP body |
DialogStore / StreamStore
|
Capped, indexed dialog / RTP-stream stores (both Debug) |
SipMessage, SipDialog, RtpStream
|
Core value types |
SipMethod, DialogState
|
The method and dialog-state enums those types report in. Both #[non_exhaustive] โ see Error handling
|
StreamKey |
What a StreamStore indexes by: SSRC plus the source and destination sockets, so one SSRC arriving on two paths stays two streams |
FilterExpr |
Compiled filter-DSL expression |
estimate_mos |
E-model MOS from jitter/loss/codec |
PcapReader yields the capture file's own records.
-
parse_packetdecodes aPacket. That is the same frame plus the facts only the caller can supply:- the timestamp at full resolution,
- the captured-vs-wire lengths,
- the link type. A multi-interface pcapng varies it per packet rather than per file.
- Building that bridge is the one step between a path and a parsed frame:
use sipnab::PcapReader;
use sipnab::capture::Packet;
use sipnab::capture::parse::parse_packet;
fn payload_bytes(path: &str) -> Result<usize, Box<dyn std::error::Error>> {
let data = std::fs::read(path)?;
let mut total = 0;
for pkt in PcapReader::new(&data)? {
let timestamp = sipnab::chrono::DateTime::from_timestamp(
pkt.timestamp_secs as i64,
pkt.timestamp_usecs * 1000,
)
.unwrap_or_default();
let caplen = pkt.data.len();
let origlen = pkt.orig_len as usize;
let link_type = pkt.link_type as i32;
let frame =
Packet::new(timestamp, pkt.data, caplen, origlen, pkt.interface, link_type);
// A capture holds frames sipnab does not decode โ ARP, a truncated
// header, an unsupported link type. That is an `Err` per frame, not a
// failed read: skipping it silently is how a partial result comes to
// look like a clean one.
if let Ok(parsed) = parse_packet(&frame) {
total += parsed.payload.len();
}
}
Ok(total)
}The parsing and capture entry points return typed, matchable error
enums. They do not return anyhow::Result.
- All three are
#[non_exhaustive]thiserrorenums (std::error::Error). - So you can propagate them into
anyhow/Box<dyn Error>unchanged. - Or you can match on their variants.
| Function | Returns |
|---|---|
parse_sip, parse_sip_bytes, parse_rtp_header, parse_sdp
|
Result<_, ParseError> |
parse_packet, PcapReader::new
|
Result<_, CaptureError> |
config::Config::load, address/rule/CIDR parsing |
Result<_, Error> |
-
ParseErrorโ protocol parsers. Variants includeEmpty { what },TooShort { what, need, got },InvalidUtf8 { what },MissingCrlf,NotSip { line },InvalidStatusCode { code },BadRtpVersion { version },SdpMissingVersion,BadSdpVersion { version }. -
CaptureErrorโ capture-file / packet decode. Variants includeTooShort { what, need, got },UnsupportedLinkType(i32),EncapTooDeep { kind, limit },NotIp { what },NoTransport,Icmp,NetMonFormat,UnknownFormat { magic }. -
Errorโ config/CLI/validation surface.ConfigRead/ConfigParsechain the underlyingstd::io::Error/toml::de::Errorvia#[source].
Match on variants rather than message text:
use sipnab::ParseError;
use sipnab::rtp::parser::parse_rtp_header;
match parse_rtp_header(&[0x80, 0x00]) {
Err(ParseError::TooShort { need, got, .. }) => {
eprintln!("need {need} bytes, got {got}");
}
Ok(header) => { /* use header */ }
Err(e) => eprintln!("parse failed: {e}"),
}Every one of these enums is #[non_exhaustive].
- So a downstream
matchmust include a wildcard arm. - sipnab can add a variant in a minor release without it being a breaking change.
- Twenty-four other public enums carry
#[non_exhaustive]for the same reason. Examples areTransportProto,RtcpPacket,FraudType,CipherSuite, โฆ. - Three public structs carry it too:
BurstGapAnalysis,RemoteReceptionReport,RemoteVoipMetrics.- A downstream literal therefore cannot construct them.
Four of the seven programs in examples/ take the library path.
- They run against a real capture and print a real answer.
- Each exists because it demonstrates something a doctest on this page structurally cannot: state accumulated across a whole file.
Of the other three:
-
uprobe_discoveranduprobe_sock_offsetsreport on the host rather than a capture. -
tls_plaintext_recordsdecodes records laid out the way the BPF TLS probe writes them, as Runnable client examples describes.
| Example | Run it against | What it is for |
|---|---|---|
call_summary.rs |
any capture with SIP | The path above carried to its end: file โ frame โ message โ call. Dialog state is what you learn from every packet in order, so one INVITE in a doctest can only ever print Trying. |
rtp_quality.rs |
rtp-protocol.pcap, then invite-opus-bye.pcap
|
Jitter, loss and MOS per stream โ none of which exist in a packet. The second capture shows StreamStore declining to score a stream whose clock rate no a=rtpmap grounded. |
filter_dialogs.rs |
any capture, plus an expression |
FilterExpr::parse once, then select_dialogs โ the join across both stores that --report and --json-dialogs also go through, and the reason those two never disagree about a capture. |
export_vcon.rs |
any capture with SIP | The same four steps plus a fifth: one reconstructed call out as a vCon container a conserver accepts. Needs --features vcon, which the default build omits. |
cargo run --features native --example call_summary -- tests/pcap-samples/register-invite-reinvite-bye.pcapOne difference bites before any other:
- The binary defaults to
--portrange 5060-5061. - The library applies no port policy at all. Nothing in
parse_packetorparse_sipfilters by port. - A capture signaling on 5080 gives:
- the library twelve SIP messages,
- the tool "No SIP signaling found" until you pass
--portrange 1-65535.
- Neither is wrong. They answer different questions.
- Knowing that is cheaper than rediscovering it against a capture you care about.
The crate is heavily feature-gated (see Cargo.toml).
- For pure parsing you only need
native. -
tls,hep,apiandmcppull in their respective subsystems. - See the feature table in install.md.