Changelog
All notable changes to sipr are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
Unreleased
Added
- Extended 3PCC (M43):
-master NAME/-slave NAMEwith-slave_cfg FILE(name;host:portlines),sendCmd dest=routed to the named peer andrecvCmd src=checked against the command’sFrom:line, on SIPp’s wiring (every instance listens on its table address; the master dials itsdest=peers at start-up, a slave dials back on first contact). Twin commands are now routed by their Call-ID like SIP messages, and the 3PCC server sides (classic controller B, every slave) open their calls on the commands that name them; an optionalrecvCmdlets a SIP message for the recv behind it pass; a closed twin connection ends the run at once with SIPp’s warning, aborting the calls still open;internal-cmd: abort_callis honored, and a classic controller sends it when it aborts a call on an unexpected message. - A documentation site at https://tareqmy.github.io/sipr/, built with
mdBook from
docs/and deployed frommasterby the new Docs workflow. - Timer and behavior knobs at parity (M42):
-max_invite_retrans,-max_non_invite_retrans(SIPp’s 5 and 9, with-max_retransas a ceiling; an INVITE’s timer keeps doubling past T2 as SIPp’s does),-recv_timeout,-timeout_error, global-lost,-pause_msg_ign,-default_behaviors(with-ndasnone) including SIPp’s abort messages (ACK/BYE/CANCEL from its own built-in templates) and its handling of unexpected BYE, CANCEL and PING,-callid_slash_ign,-sleep,-nostdin;-send_timeoutand-timer_resolare accepted with a warning. The[last_Request_URI]keyword. - Message and error logs at parity (M41):
-trace_msgframes and-trace_errlines take SIPp’s exact shapes (timestamps, theThe following events occurred:header),<log>actions go to the new-trace_logs/-log_file, and-trace_shortmsg/-shortmessage_file,-trace_calldebug/-calldebug_file,-error_file,-message_file, the-<kind>_overwriteflags,-ringbuffer_files/-ringbuffer_size/-max_log_sizerotation and-deadcall_waitare implemented as SIPp’s;-trace_timeoutis accepted (a no-op in SIPp too).trace logs|shortmessages on|offwork on the control socket. - Statistics files at parity (M40):
-trace_statwrites SIPp’s full column set (StartTime…WatchdogMinor,ResponseTime<rtd>mean and standard deviation per RTD,CallLength, a repartition block per RTD and for the call length, SIPp’s time formats and trailing delimiter); new-trace_rtt/-rtt_freq,-trace_counts,-trace_error_codes,-trace_screen/-screen_file,-stat_delimiter,-periodic_rtdand-f, all with SIPp’s file names and formats. - Keyword parity (M39):
[clock_tick],[timestamp],[date],[sipp_version],[dynamic_id],[remote_host],[tdmmap],[last_message],[last_cseq_number](with+N/-N),[fill variable= text=]and[file name=]render as in SIPp, and-key KEYWORD VALUEdefines generic keywords. New flags-tdmmap,-dynamicStart/-dynamicMax/-dynamicStepand-rfc3339.[timestamp]is UTC where SIPp uses local time (SIPP_COMPAT §6). - Statistical pauses at SIPp parity:
<pause distribution="…">now takes SIPp’s attribute form (distribution="normal" mean="…" stdev="…") and all ten of SIPp’s kinds —fixed,uniform,normal,lognormal,exponential,weibull,pareto,gpareto,gamma,negbin— plus the old-style<pause min= max=>spellings and SIPp’ssanity_check. The<sample assign_to= distribution=…>action draws into a variable. A pause sample below 1 ms is no pause, as in SIPp. The interop CI build of sipp now includes GSL so the comparison runs both ways.
Changed
- A 3PCC twin command must carry the call’s
Call-ID:(SIPp routes by it); sipr used to hand a command to whichever call was waiting. Classic controller B no longer paces its calls with-r: as in SIPp they open when controller A’s command arrives. - Non-INVITE messages retransmit up to 9 times by default (SIPp’s
-max_non_invite_retrans), not 5; an aborted client call now sends SIPp’s BYE/CANCEL/ACK unless-ndor-default_behaviors …,-bye; a///prefix in an inbound Call-ID is stripped as SIPp’s 3PCC marker unless-callid_slash_ign. - The
-trace_msgframe no longer carries the peer address, and-trace_errlines are timestamped: both are now SIPp’s formats.<log>lines no longer go to the error trace with a[log]prefix; they need-trace_logs. -fddefaults to 60 s as in SIPp (it was 1 s); the final statistics row is still written at exit. The-trace_statheader changed from sipr’s earlier subset to SIPp’s columns, so parsers keyed on column position need SIPp’s layout.--checkand the scenario screen label a distributed pause the way SIPp’s screen does (N(60000.000,15000.000),Exp(…),Wb(…), …).- The scenario-side positional form
distribution="uniform(200,3000)"still parses but is documented as a sipr extension; SIPp’s attributes are canonical.poisson, which SIPp never had, is now an error.
Fixed
- On Windows an ICMP port-unreachable for a peer that is down no longer
kills the UDP socket: the receive loop rides out
ConnectionReset(theWSAECONNRESETquirk on unconnected sockets) instead of ending the run with “socket error”.
0.27.1 — 2026-09-20
Changed
- The release workflow publishes crates one at a time, skipping versions already on crates.io, so a partial publish resumes instead of failing on the first already-published crate. A manually triggered “Publish crates” workflow does the same for any existing tag.
0.27.0 — 2026-09-20
Security
- rustls updated to 0.23.45 for RUSTSEC-2026-0285 (TLS 1.3 handshake messages accepted across encryption level boundaries).
Added
- Release automation modelled on gitwig’s: a tag-triggered
CDworkflow that builds static Linux (x86_64, arm64), macOS (Intel, Apple Silicon) and Windows binaries, publishes the GitHub release, and, when the matching secret is present, publishes the crates to crates.io, updates thetareqmy/homebrew-tapformula, and pushes a Chocolatey package. - Install and uninstall scripts for macOS/Linux (
scripts/install.sh) and Windows (scripts/install.ps1), a Nix flake, a reference Homebrew formula, anddocs/INSTALLATION.mdcovering every method. cargo-denypolicy (deny.toml) checked in CI, and a CI portability job building and testing on macOS and Windows.SECURITY.mdandCONTRIBUTING.md.
Changed
- TLS PEM files (
-tls_cert,-tls_key,-tls_ca,-tls_crl) are now parsed withrustls-pki-types, the crate rustls itself uses, replacing the unmaintainedrustls-pemfile(RUSTSEC-2025-0134). Same formats, and a malformed file is now reported with its name. - README rewritten to describe sipr’s relationship to SIPp neutrally, state
the benchmark numbers
benches/BASELINES.mdactually records, and list every deliberate compatibility gap.
Fixed
- The call pacer now credits new calls from elapsed wall-clock time
(
rate × elapsed / rate_period, as SIPp does) instead of counting timer ticks, so a delayed or coalesced tick no longer lowers the achieved rate.
0.26.0 — 2026-09-19
Added
exec command=(M37): run a shell command from an action, with keywords and variables rendered into it. Fire-and-forget as in SIPp, but spawned and reaped by one runner thread, so the engine never forks or blocks and no zombies accumulate under load.<setdest host= port= protocol=/>(M37): send the rest of a call to another peer — retargeting over UDP, re-dialling the call’s own connection over per-call TCP — with every check SIPp makes and its wording. A rejectedsetdestfails that call rather than the run.[remote_ip]/[remote_port]keep the nominal remote, as in SIPp.ereg search_in="body"andsearch_in="var" variable="…", which SIPp’s documented setdest idiom relies on.
Notes
- Three SIPp behaviors met on the way are recorded in
docs/SIPP_COMPAT.md§6 and deliberately not copied:[next_url]needsrrs="true"to carry the Contact in SIPp,[last_*]inside a recv’s own actions still name the previous message there, and SIPp’secho [last_From]example breaks under any shell without quoting.
0.25.0 — 2026-09-19
Added
- Manual transactions (M36):
start_txnandack_txnon<send>,response_txnon<recv>. A recv so named matches only a response whose top Via branch is the one its request carried, which tells concurrent transactions of the same method apart. SIPp’s placement and usage errors are reported with its wording, requests naming a transaction leave the CSeq-method guard list, and late responses to a named transaction are handled as SIPp does: a provisional is ignored, a final one for an INVITE transaction gets the recorded ACK sent again, and a repeat of the final already taken is ignored.--checklists the transactions. Verified against real sipp both ways.
0.24.0 — 2026-09-19
Added
- Dynamic users (M35):
<User variables="…"/>and<Global variables="…"/>give variables a life beyond the call — one table per user id, kept for the run, and one for the whole process, shared by both scenarios — resolved at compile time into a layered store with no allocation on the hot path.set usersnow follows SIPp’s id bookkeeping exactly: the pool is served from the back, a call ending while more calls are live than the target retires its id, and a later growth reactivates retired ids (with their variables) before creating fresh ones. SIPp’s-set VARIABLE VALUEseeds a global;dump variablesover the control socket lists the scopes;--checkprints them. Verified against real sipp both ways.
Fixed
- Variable values now render and test exactly as in SIPp: a double is
written with
%lf(3.000000, sipr used to print3), a true bool astrue, and a zero double, a false bool or an unset variable as nothing at all;test="var"andcondexecon a message take the same “is set” view (a"0"or"false"string is set, a zero counter is not). Scenarios that compared a rendered counter against3should compare against3.000000— that is what SIPp sends.
0.23.0 — 2026-09-13
Added
- Mixed mode (
-rxsf <file>/-rxsn <name>,-rxinf <file>) — a second, server-mode scenario terminates the calls the peer originates towards us while the client-mode main scenario originates ours, as in SIPp: own statistics, rx calls never counting toward-m/-l/-users,-rxinffiles joining the injection table after the-infones, andset display rxover the control socket. sipr enforces the role rules SIPp’s help text only promises. Verified against real sipp both ways.
Changed
set display ooc|main|rxnow switches every screen — counters, statistics, repartitions and the scenario page — to the displayed scenario, as SIPp does; 0.22.0 swapped only the scenario page. The HTTP/statsdocument gains amixedflag.
0.22.0 — 2026-09-12
Added
- Out-of-call scenarios (
-oocsf <file>/-oocsn <name>) — a second, independently compiled scenario that answers requests mapping to no known call, as in SIPp: client mode only, the embeddedooc_defaultandooc_dummy(dumpable with-sd), own per-step statistics, no[fieldN]/-infin ooc calls, and ooc calls never count toward-m/-l/-users.set display ooc|mainover the control socket swaps the TUI scenario page. Verified both ways against real sipp.
Fixed
regexp_match="true"on<recv>was parsed but never applied by the matcher, so arequest=".*"step matched nothing.
0.21.0 — 2026-09-06
Added
- SCTP transport (
-t s1|sn) behind the new off-by-defaultsctpcargo feature, viasocket2: one SCTP message per SIP message as SIPp does, mono and per-call associations, reconnection. Needs an OS SCTP stack at run time (Linux with thesctpmodule); SIPp’s SCTP option flags are rejected with an explanation. Exercised in Linux CI against a SIPp built withUSE_SCTP.
Fixed
- CI now runs on pushes to
master(it only ran for pull requests) and builds SIPp 3.7.7 from source for the interop suite; a few tests that raced the echo threads’ counters or assumed macOS socket timing are settled.
0.20.0 — 2026-09-05
Added
-t ui— one UDP socket per IP address from the injection file (-ip_field): each client call sends from its line’s IP, a server binds every listed IP and answers on the one the request hit, and the new[server_ip]keyword renders the IP a call sends from — SIPp’s per-IP mode for emulating many user agents.
0.19.0 — 2026-09-05
Added
- TCP/TLS reconnection —
-max_reconnect,-reconnect_close,-reconnect_sleep: when the mono TCP/TLS connection drops, the calls on it fail (or, with-reconnect_close false, live on), the call whose send finds it dead fails, and the connection is re-dialed within the budget after the sleep — SIPp’s reset, in SIPp’s order; with no budget left the run ends with exit 255 like SIPp’s fatal error. Verified both ways against real sipp.
0.18.0 — 2026-09-05
Added
-rsa host[:port]— the remote sending address: a UAC sends every message there instead of to the target, a UAS answers there (from a socket of its own) instead of to the request’s source, and the keywords keep naming the nominal remote, as in SIPp. Verified both ways against real sipp.
0.17.0 — 2026-09-05
Added
- Per-call sockets —
-t un,-t tn,-t ln: every call opens its own UDP socket or TCP/TLS connection at its first send (SIPp’s multisocket modes),[local_port]names it, and-max_socketcaps how many are open before calls share them round-robin.<closecon/>now closes a per-call socket for real. Verified both ways against real sipp.
0.16.0 — 2026-09-05
Added
- SIPp’s unexpected-message handler —
<label id="_unexp.main"/>,_unexp.retaddr,_unexp.pausedaddr,<jump variable=>and<pauserestore>: an unexpected in-call message jumps to the handler, which answers it and resumes the interrupted pause for exactly its remaining time. Verified both ways against real sipp. <closecon/>is accepted (a no-op, as SIPp’s reference-count drop is on every mono-socket transport).
0.15.0 — 2026-09-05
Added
<verifyauth>— sipr can play a digest-checking registrar: the receivedAuthorization:header is verified against a username and password (MD5 or SHA-256, qop auth/auth-int,-auth_uri) and the boolean verdict drivestest=branching, exactly SIPp’s documented recipe. Verified in both directions against real sipp.
0.14.0 — 2026-09-05
Added
- SRTP echo server —
exec rtp_echo="startaudio|updateaudio|stopaudio| startvideo|updatevideo|stopvideo[,pt[,name]]": the call echoes (S)RTP on its advertised media port, re-keyed from the SDES negotiation with the caller’s SSRC and sequence numbers preserved. SIPp’spfca_uas_*_crypto_*.xmlscenarios now run unchanged, and real sipp’s UAC passes its own RTP check against them.
Fixed
ereg search_in="hdr"now hands the regexp what SIPp does: the rest of the first matching line after the header string (soheader="CSeq:"works andCSeq: [$1]replays the caller’s CSeq), and an absent header fails the call undercheck_it.
0.13.0 — 2026-09-05
Added
[authentication]from an injection field — a CSV column holding[authentication username=… password=…](or AKA parameters) is re-parsed as the keyword at send time, SIPp’s documented way to give each call its own credentials.
Fixed
[authentication]now renders the whole header line as SIPp does (Authorization:after a 401,Proxy-Authorization:after a 407), so SIPp scenarios that place the keyword on its own line work unchanged. sipr’s earlierAuthorization: [authentication …]spelling still works.
0.12.0 — 2026-09-05
Added
- SRTP with SDES keying — SIPp’s crypto keywords (
[cryptotag1audio],[cryptosuiteaescm128sha1801audio],[cryptokeyparams1audio], theue…unencrypted forms, secondary and video variants) render offers and answers; the peer’sa=crypto:lines are parsed;rtp_streampackets are protected with AES-CM-128 or the NULL cipher and HMAC-SHA1 80/32, and the echo check unprotects the echo before comparing. All cryptography is in-tree and verified against RFC 3711’s vectors. Unlike SIPp, the authentication tag uses the packet’s own rollover counter, so streams stay valid past sequence 65535.
Fixed
- The CSeq-method guard on
recv response=now follows SIPp exactly: a response matches when its CSeq method is any request method sent so far, not only the most recent one. A 200 to the INVITE arriving after a PRACK was wrongly treated as unexpected.
0.11.0 — 2026-09-04
Added
hideanddisplayattributes, and SIPp’s screen keys —hide="true"keeps a step off the scenario screen whileset hide true(the default) holds;display="…"replaces its label. Both reach/stats. The1/2/3keys switch screens at the keyboard and over the control socket.
0.10.0 — 2026-09-04
Added
-auth_uri— SIPp’s flag for the digesturi=; the value gets asip:prefix exactly as SIPp does.- Keywords inside
[authentication]parameters —username=[field0],password=[$p],aka_K=[field2]and friends are rendered before use, as SIPp renders them, so credentials can come from injection files.
Changed
- The default digest
uri=is now SIPp’ssip:remote_ip:remote_port(no user part) instead ofsip:service@remote_ip:remote_port. Servers verify against the header’s ownuri=, so runs are unaffected; the wire form now matches SIPp byte for byte.
0.9.0 — 2026-09-04
Added
- Rate ramps — SIPp’s
-rate_increase N,-rate_interval TIME(seconds orms/s/m/h),-rate_max N, and-no_rate_quit: the rate climbs every interval and, when it would pass the cap, is clamped there and the run drains (unless told not to). Also-rate_scalefor the hot-key step.
0.8.0 — 2026-09-04
Added
- AKA resynchronisation (AUTS) —
[authentication … aka_sqn=0x…]gives the client’s SQN_MS; a challenge whose SQN is not above it (or any challenge withaka_resync=1) is answered withauts=and an empty-password digest per RFC 3310 §3.2 / TS 33.102 §6.3.3, then the server’s fresh challenge is answered normally. SIPp’s resync code is unreachable, so this is new ground for SIPp scenarios.
0.7.0 — 2026-09-04
Added
- RTP echo and the RTP check —
-rtp_echo(with-mb) echoes RTP received on the media port and media port + 2 back to its sender, with SIPp’s counters and the<rtp_echo value="0|1"/>action to toggle it;rtp_streamsockets now read back what the peer echoes and compare it to what was sent, and-audiotolerance/-videotoleranceturn that into SIPp’s verdict: a failed check exits 253 (SIPp’s -3). Unlike SIPp, a stream is judged only when a tolerance flag is given. New counters on the TUI, the-bgline, and/stats.
0.6.0 — 2026-09-04
Added
- Runtime control — SIPp’s UDP control socket (
-cp,-ci: hot keys andc-prefixedset/trace/dump/resetcommands with SIPp’s grammar and warning texts; default bind is loopback and-cp 0disables it) and a new HTTP/JSON API (--sipr-http [HOST:]PORT,--sipr-http-token):/health,/stats,/control,/quit,/command,/scenario. Seedocs/CONTROL_API.md. New std-only cratesipr-control. - Hot keys now follow SIPp exactly:
set rate-scalesteps, user-count keys in-usersmode, and a secondqaborts likeQ.
0.5.0 — 2026-09-04
Added
- IMS AKA authentication (
AKAv1-MD5, RFC 3310) —[authentication aka_K=0x… aka_OP=0x… aka_AMF=0x…](SIPp’s parameters, plusaka_OPc=) against analgorithm=AKAv1-MD5challenge: the nonce’s RAND/AUTN go through an in-tree Milenage (AES-128, verified on 3GPP TS 35.208 test sets), the MAC is checked, and RES becomes the digest password. A MAC mismatch fails the call with a clear reason where SIPp aborts the whole process. No new dependency.
0.4.0 — 2026-09-04
Added
- RTP streaming and DTMF (
exec rtp_stream=,exec play_dtmf=) — SIPp’srtpstream.cppsemantics on the M14 scheduler: raw codec files orapattern/vpatternfills with SIPp’s fixed payload table, looping,pause/resume(the clock keeps running, as in SIPp), SSRC0xCA110000-based, plus RFC 4733 DTMF bursts with SIPp’s exact timing.[rtpstream_audio_port]/[rtpstream_video_port]keywords with per-call allocation,-rtp_payload,-max_rtp_port,-random_base_ssrc. sipr streams from the port the SDP advertised (SIPp binds an unrelated one) and numbers DTMF packets consecutively (SIPp skips every other warm-up number). Divergences indocs/SIPP_COMPAT.md§6.
0.3.0 — 2026-09-04
Added
- pcap replay (
exec play_pcap_audio|video|image=) — SIPp’s media feature, without the raw socket: a new std-onlysipr-mediacrate reads classic pcap files (Ethernet/802.1Q, raw IP, Linux cooked, BSD loopback; IPv4/IPv6 UDP), learns the peer’s media endpoint from its SDP, and replays the UDP payloads verbatim on the capture’s timeline from one scheduler thread, through ordinary UDP sockets bound to the advertised media port — no root, no libpcap.-mi/-mp(-min_rtp_port),[auto_media_port],[media_port+N],<recv ignoresdp>. RTP counters on the TUI,-bgline, and the final summary. Divergences from SIPp indocs/SIPP_COMPAT.md§6.
0.2.0 — 2026-09-03
Added
- TLS transport (
-t l1) — SIP over TLS with SIPp’s exact semantics: same connection-per-peer model and Content-Length framing as TCP, no SIP retransmissions, port 5060,[transport]rendersTLS.-tls_cert/-tls_key(defaultscacert.pem/cakey.pem),-tls_ca/-tls_crl(presence enables SIPp-style verification: chain but not hostname on the client, mandatory client cert on the server),-tls_version 1.2|1.3. Built onrustlswith theringprovider — the workspace’s first external dependency, still no system OpenSSL required. Divergences from SIPp documented indocs/SIPP_COMPAT.md§6.
Changed
-
The
dependencies: std-onlyclaim is retired:sipr-netnow carriesrustls/rustls-pemfilefor the TLS transport. Everything else remains std; the build still needs no system libraries. -
IPv6 — targets accept bracketed (
[::1],[2001:db8::1]:5060) and bare-literal (::1) IPv6, with automatic::binding when a v6 target is given without-i.[local_ip]/[remote_ip]render bracketed inside URIs and Via (SIPp’slocal_ip_w_brackets), while[media_ip]stays raw for SDP. Seedocs/SIPP_COMPAT.md§6.
0.1.1 — 2026-08-17
Post-v1 feature drop: TCP transport, injection files with indexed lookups,
classic 3PCC, and closed-loop -users mode. Still standard-library only.
Added
- Closed-loop
-users—-users Nkeeps N concurrent calls, each holding a stable 1-based user id; a finished call’s id is recycled into a replacement immediately. Adds the[userid]/[users]keywords and lights up USER-mode-infinjection (line = user id − 1). Mutually exclusive with-l. Seedocs/SIPP_COMPAT.md§6. - Classic 3PCC —
-3pcc HOST:PORTplus<sendCmd>/<recvCmd>steps let two sipr instances coordinate over a separate ESC-delimited TCP “twin” socket. The role is derived from the scenario’s first twin command (sendCmd-first dials,recvCmd-first listens);<recvCmd>blocks the call until a command arrives and runs its actions against the command text. Seedocs/SIPP_COMPAT.md§6. - TCP transport —
-t t1runs SIP over TCP. A stream framer de-frames messages byContent-Length(RFC 3261 §7.5); the client keeps one connection to the target, the server accepts connections and replies on the one each request arrived on. Reliable transport, so no SIP retransmissions are scheduled.-t tnis accepted as an alias. Seedocs/SIPP_COMPAT.md§6. - Injection files —
-inf FILE(repeatable) loads SIPp-style injection files: aSEQUENTIAL/RANDOM/USERmode header,;-separated fields,#comments, blank-line terminator. One line is drawn per call per file (SEQUENTIAL cycles, RANDOM picks uniformly, USER defers to-users). The[fieldN]keyword substitutes field N of the drawn line.file=NAMEselects another file by its basename (SIPp’s key), or by a 0-based-infindex (sipr extension);line=overrides the per-call line and is rendered at send time, soline=[$var]works. Unknown field/file names are rejected at load. Seedocs/SIPP_COMPAT.md§6. - Indexed injection —
-infindex FILE FIELDbuilds a key→line index over one field of an-inffile (matched by basename; last line wins on duplicate keys). Actions<lookup assign_to=… file=… key=…/>(stores the matched line or -1),<insert file=… value=…/>(appends a row), and<replace file=… line=… value=…/>(swaps a row) operate on that data at runtime. The canonical use islookup → [fieldN line=[$var]].
Fixed
- Body-less SIP messages (180, ACK, empty 200) now always include the
mandatory
\r\n\r\nheader/body separator. UDP tolerated its absence; TCP framing and real-SIPp interop require it.
0.1.0 — 2026-08-16
First release. A SIPp-compatible SIP testing tool and traffic generator, feature-complete for signaling over UDP. Built entirely on the Rust standard library — no external crates.
Added
- Scenarios — SIPp-compatible XML:
send,recv,pause,nop,label,timewait,Reference, and the response/call-length repartition tables. Loud diagnostics with file:line context;--checklint mode that prints the compiled IR. - Keywords —
[service],[remote_ip]/[remote_port],[local_ip]/[local_port],[transport],[call_id],[call_number],[cseq],[branch],[msg_index],[pid],[routes],[next_url],[peer_tag_param],[len],[last_*:],[$var],[authentication], and the[media_*]placeholders. - Actions —
ereg(capture groups via an in-tree POSIX-ERE engine),assign/assignstr/strcmp/test, arithmetic (add/subtract/multiply/divide),todouble,trim,urlencode/urldecode,gettimeofday,jump,log/warning/error,exec int_cmd. Plustest/condexecbranching,chance, named counters, and per-call variables. - Engine — UAC and UAS roles; open-loop pacer (
-r/-rp/-l/-m) with rate smoothing; single event-loop over UDP, timers, and the pacer. Recv matching (optional-recv windows, backward contiguous scan, CSeq-method guard) verified against SIPp’scall.cpp. - Transport — UDP with RFC 3261 T1→T2 retransmission, inbound
retransmission handling, simulated loss (
lost), and an in-tree SIP message parser proven panic-free by a fuzz suite. - Authentication — digest MD5 and SHA-256 (
qop=auth, proxy 407), with in-tree hash primitives verified against the RFC 1321 / FIPS 180-4 / RFC 2617 / RFC 7616 vectors. - Statistics — SIPp counter set with failure breakdown, response-time
histograms and RTDs, repartition tables,
-trace_stat/-stf/-fdCSV, and-trace_msg/-trace_errfiles. - Live TUI — main / per-step scenario / repartition screens, live rate
keys (
+ - * /), pause (p), screen cycling (s), and safe terminal restore on every exit path. Ferrous brand colors, honoringNO_COLOR. - CLI — SIPp-style single-dash flags with did-you-mean suggestions; SIPp-compatible exit codes (0 ok, 1 failures, 99 no calls, 2 usage, 255 fatal).
- Tooling — six-crate workspace,
Makefileconvenience targets, CI (fmt + clippy + tests, and an interop job against real SIPp), and the Ferrous brand kit underbrand/.
Known limitations
- Signaling only over UDP. TCP/TLS,
-infinjection files (and thelookup/insert/replaceactions), 3PCC, RTP/pcap media, IPv6, and an HTTP control API are on the post-v1 roadmap. - The
eregregex engine is leftmost-first greedy (PCRE-style), not POSIX leftmost-longest — identical on the patterns real scenarios use; seedocs/SIPP_COMPAT.md§6.