Status and known gaps
The port is 96.1% ported by C lines of code (a weighted estimate), and 90 of 90 replay scenarios match the C server's recorded transcripts. Both numbers come from parity/status.json, the same file that drives the progress bars in the README.
What these numbers do not say
Ported means the logic is written and unit-tested in Rust. Verified means a scenario's exchange with the Rust server equals the one recorded from the C server. Neither means a browser can use the feature end to end. Janus Bench does that, and its results show where a component with a high percentage still fails with real media: the VideoRoom subscriber offer, RecordPlay playback and the AudioBridge mix are examples.
Oracle: the C server at commit bc0de3b3. Deliberate differences and open questions are in PARITY_LOG.md, summarised at the bottom of this page.
Core
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Core dispatch, sessions, plugin traits | 7346 | 99% | Open: the plugin's own a=mid lines are not stripped before the merge (sdp.c 782-800); the RTX gate on plugin offers is an approximation. Open: the outgoing stats expiry, janus_ice_media_stopped, and surfacing PeerEvent::NoMedia. Open: a renegotiation still replaces the PeerSession, so DTLS, NACK, TWCC and slow_link state is lost (janus-webrtc). |
| Config and CLI options | 795 | 92% | Open: the TextRoom header in janus/src still writes UTC, and the libconfig hex, bytes and NaN gaps. Open: lone bytes 0x80-0xFF need an owner decision on a byte-string value model; the hex radix is a path-keyed set, not a ConfigValue field (owner review). |
| Utilities and IP handling | 1853 | 88% | Live getifaddrs, DNS, pidfile and filesystem helpers are I/O, not ported. Open: the clock, mkdir and pidfile helpers need the OS, and gzip needs zlib. |
Full notes for this group
Core dispatch, sessions, plugin traits
Client API, sessions, relay and ordering, admin executor, attach and JSEP checks, token auth with signed tokens through janus-crypto, port-range decision carried to the ICE config, ApiConfig from options, PeerConnection and medium summary builders, packet media types. Missing: loop_index in ICE, the nice call, caller wiring of the summaries, janus.c long tail. A per-handle PeerSession store is kept on JanusApi, removed on detach and destroy. Hangup clears it with the ICE agent. The admin handle_info WebRTC block is wired from tracked state; gathered, connected, DTLS, msid and stats are left unset. JSEP offer processing: a remote offer is parsed, stored as a PeerSession, and answered per m-line with ICE credentials, candidates, mid, BUNDLE and ice-options. The plugin still makes the codec choice. A missing setup defaults to actpass as C does. The JSEP answer also carries a=mid per m-line, a=group:BUNDLE over the answered mids, and the global a=ice-options:trickle, as C's janus_sdp_merge writes them. The stored PeerSession keeps one entry per offer m-line: mid, direction, payload types, SSRCs and extmaps. The offer's msid, mstid and the RTX-to-primary payload mapping are kept too; the admin block reports the offer msid as the remote stream and track, and reports connected once the agent has selected a pair. An incoming JSEP offer is processed on the message path before the plugin call, as C does (janus.c 1540-1810); the answer comes later from the plugin's answer. The plugin's hangup_media runs on hangup, before the teardown. The message path creates no ICE agent or DTLS identity itself, so offers pass through unprocessed until an identity is set. The binary registers built-in plugins at startup, unless [plugins] disable names them, as janus.c 5794-5822 does. Seven are registered (echotest by default); recordplay, nosip, duktape and lua wait for their constructor inputs. Checked live: echotest attaches over HTTP and answers a message. A plugin whose init fails is skipped with a warning, and startup goes on (janus.c 5879-5884). RecordPlay and Duktape register from their config files; Duktape is behind its feature and checked live. A plugin's JSEP answer on an attached handle is merged with the handle's ICE credentials, candidates, DTLS role and fingerprint, and pushed. An answer on a handle with no ICE agent is refused with the C error. A plugin offer on a handle with no agent creates one, controlling, with a random tie-breaker and credentials and the host candidates of every up non-loopback IPv4 interface. The enforce and ignore lists are read from nat.ice_enforce_list and nat.ice_ignore_list. On a selected ICE pair the core starts DTLS for the handle's PeerSession, and calls the plugin's setup_media once SRTP is keyed. The created event carries the transport package and id, as janus.c 1177-1188 does (register_transport_package). The C routing predicate for messages is ported. The message path is serialised through the core's single request gate, so each request's events keep their order. C runs plugin messages on task_pool_size threads. Concurrent plugin calls need per-request ordering state and calls made outside the core lock, which is a redesign not made here (Wave 128 decision). RTCP that a plugin relays goes out to its subscriber, filtered as janus_rtcp_filter does and protected on the selected pair, and REMB messages queued with send_remb go out on the same path (Wave 127). Plugin RTCP relays keep their SSRCs, as C does (janus_ice_relay_rtcp has no medium). Plugin sockets can be released, and the socket layer closes them (Wave 130). Every plugin offer on an attached handle is merged, and one with restart restarts ICE with new local credentials and the peer's kept (Wave 131, janus.c:3799-3880 and 4123-4124). A client answer to a plugin offer is processed for ICE: the peer credentials are set on the agent, a change restarts ICE, and the first m-line candidates are added (Wave 132, janus.c:1700-1800, sdp.c:403-411). A client offer with new ICE credentials restarts ICE too (Wave 133). A client answer to a plugin offer creates the PeerSession of that offer when the handle has none: the answer's a=setup gives our DTLS role, its fingerprint the peer's, and its m-lines the media layout (Wave 134). A two-peer test keys SRTP on both sides over the in-process ICE and DTLS loop. Still open: the offer's media layout is taken from the answer, not the offer, and the plugin offer does not get its own PeerSession before the answer. Data channels: the PeerSession of a negotiated application m-line runs the SCTP association over DTLS (Wave 136). DCEP and the plugin data path are the next steps. Plugin data channels: incoming data goes to incoming_data, and relay_data sends on the open channel (Wave 137). Plugin data relays open a channel for a new label and send on it (Wave 140). Wave 150: send_pli and close_pc in CoreCallbacks, with the ALERT flag and the pending close flush from tick. Wave 151: client hangup event (Janus API), admin hangup event (Admin API), handle ALERT at attach, and the Close PC and WebRTC state events. Wave 155: empty RTP and RTCP relays are refused, and relays to an alerted handle are refused (janus.c 4273-4302). session_of stays as C's push_event, which gates on STOP only. Wave 156: PLI, stream PLI and REMB are gated on ALERT and STOP, and the close flush skips a stopped handle (janus.c 4310-4349). The recording seam has pause and resume, not yet on the plugin callbacks. Wave 157: pause_recording and resume_recording are core callbacks, forwarded through OrderedCallbacks to the recording seam. Wave 158: flush-time gate for PLI and REMB requests, dropping a request whose handle was alerted, stopped or detached after queueing (ice.c 4714, 1673, 1734). Wave 159: RecordingRequest carries extmaps, written into the .mjr header. Wave 164: hangup clears the negotiation flags (ice.c 1645-1649), and setup_ice refuses an existing agent (ice.c 3586-3590). Wave 168: the bundle mid is compared (ice.c 987-990), and webrtcup goes once per handle, guarded by READY (ice.c 5298-5358). Open: a first client offer with no agent is still refused (janus.c 1590-1592, owner decision because a videocall test pins it); the -2 refusal for an existing agent (ice.c 3586-3590); the binary must drain take_webrtc_events(). Wave 170: the binary drains webrtcup and the admin hangup into the session queues in order (ice.c 5298-5358, janus.c 2993). Open: pfunix, MQTT and RabbitMQ sessions get no events; the HTTP long-poll token check needs a public core API; the ConsentLost hangup. Wave 171: the ICE failure path maps consent loss onto C's FAILED state (ice.c 2138-2155, 2019-2071, default off), with an RNG injection seam. This changes core behaviour on consent loss; owner to confirm. Wave 175: the negotiated video codec sets the NACK keyframe check (ice.c 4907-4915), and the RTX duplicate verdict now gates delivery to the plugin (ice.c 2787-2801). Open: the per-medium keyframe check for mixed audio and video sessions (needs one PeerMedia per stream), and the client-answer local offer (C uses our offer). Wave 176: out-of-request events reach pfunix and MQTT clients through weak session routes in the binary; the core dispatch is unchanged. Wave 179: the NACK keyframe codec of a client answer comes from the merged local SDP (janus.c 4266-4267, ice.c 2831, 4900). Open: the per-medium keyframe check for mixed sessions (janus-webrtc change). The core exposes check_auth_token, so the binary's long-poll token check accepts valid tokens (auth.c 208-221). Wave 182: the NACK keyframe check is per medium (ice.c 2836-2848, 2958-2966, 4905-4915), and a NULL plugin info key is omitted (janus.c 501-506). Open: RTX types from the offer's apt mapping (sdp.c 579-604), the per-medium rtcp context, TWCC, REMB and the outgoing NACK store, and the outgoing keyframe clear (ice.c 4922-4931). Wave 183: each medium has its own NACK queue, RTCP context and slow_link state, RTX types come from the offer's apt mapping, and the outgoing keyframe clear follows janus_cleanup_nack_buffer (ice.c 3017-3250, 3142-3162, 4349-4393, 4922-4931, sdp.c 579-604). TWCC and REMB stay shared, as C does. Open: the janus-sdp answer drops RTX types (sdp.c 1593, 1678-1697); PeerEvents keeps one slow_link event per poll; the unknown-SSRC NACK fallback (ice.c 3119-3125). Wave 185: the answer gets the FID group and cname lines from SSRCs drawn per answered m-line and kept across renegotiation (ice.c 1865-1868; sdp.c 897-912, 1678-1706). Open: plugin offers draw no SSRCs, and msid lines in the merged answer (sdp.c 1694-1700). Wave 186: plugin offers draw SSRCs per m-line, and the merged SDP writes the FID group, msid and SSRC lines before the candidates (janus.c 3868; sdp.c 897-912, 1678-1706, 1686-1700, 1734). Open: the plugin's own a=mid lines are not stripped before the merge (sdp.c 782-800); the RTX gate on plugin offers is an approximation. Wave 188: the media-state events and the once-per-second tick are wired, and the plugin's own a=mid is stripped (ice.c 2919-2939, 4439-4468; sdp.c 782-800). Open: the outgoing stats expiry, janus_ice_media_stopped, and surfacing PeerEvent::NoMedia. Wave 209: a renegotiation that drops an m-line keeps its identity as a retired medium, and the media-state walks visit it in m-line order (ice.c 4439-4468, 4617-4636). The drop alone raises no stopped notice, as C only queues the handler on processed m-lines (sdp.c 226-227). Open: a renegotiation still replaces the PeerSession, so DTLS, NACK, TWCC and slow_link state is lost (janus-webrtc). Wave 247: every C event type is raised to handlers; events.broadcast, stats_period, combine_media_stats honoured; plugin socket handoff (take_pending_relays). Wave 248: admin handle_info stats fed from the media state; admin commands compared with the oracle (admin-* scenarios); Plugin::supports_admin_messages; relay_rtp, relay_rtcp and data_ready reach plugins.
Config and CLI options
jcfg read and write match libconfig 1.5.0 golden output; @include on in-memory sources; INI; all 44 CLI options. Wave 177: the scalar item text follows config.c 62-126. Wave 191: janus-config exports the local header text through localtime_r (config.c 678-685), with UTC as the fallback. Open: the TextRoom header in janus/src still writes UTC, and the libconfig hex, bytes and NaN gaps. Wave 240: the hex radix (0x%X of the low 32 bits; 0x%llX with L) and NaN (nan.0, -inf.0) match libconfig 1.5.0, pinned by goldens captured in Docker (hex_forms, hex_nested, hex_low_bits, nan_api). The reader keeps the low 32 bits of a no-L hex literal. Open: lone bytes 0x80-0xFF need an owner decision on a byte-string value model; the hex radix is a path-keyed set, not a ConfigValue field (owner review). Wave 242: ConfigValue::Bytes holds strings that are not valid UTF-8, and write_jcfg_bytes reproduces the libconfig 1.5.0 bytes golden byte for byte.
Utilities and IP handling
Pure helpers, address logic, RED block PT replacement. Live getifaddrs, DNS, pidfile and filesystem helpers are I/O, not ported. Wave 224: the H.264 I-frame and B-frame NAL checks (utils.c 867-910) and the protected-folder add and clear rules (utils.c 579-624) are ported, with 12 tests. Open: the clock, mkdir and pidfile helpers need the OS, and gzip needs zlib. Wave 239: IPv6 groups with five or more hex digits are refused, including leading zeros; confirmed against the oracle image's glibc inet_pton (00001:: and 0001234::1 are refused, 0001:: is accepted). Wave 247: live clock, mkdir, pidfile, interface and gzip helpers.
Media
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| ICE and TURN REST | 5665 | 95% | Open: consent and keepalive timers, ice-lite, full_trickle. Open: ICE-TCP pairs (the agent advertises TCP candidates but forms no TCP pair), the SRTP SHA1_32 profile (dimpl 0.9.0). Open: TCP keepalive/reconnect, TCP-SO, TURN over TCP, IPv6 TCP candidates, a stream that closes under a selected pair. |
| DTLS | 1344 | 70% | Open: janus-core must call these on DtlsEvent::PeerCertificate and run the retry tick and retransmit counter; the SRTP SHA1_32 profile is blocked on dimpl 0.9.0; state notify on every handshake call is an owner decision. Open: the SRTP SHA1_32 profile (dimpl 0.9.0), and state notify on every handshake call (owner decision). Open: SHA1_32 profile (dimpl 0.9.0). |
| SCTP and data channels | 1448 | 96% | Open: usrsctp performs a peer SSN/TSN reset and answers Performed; janus-sctp answers Denied. Open: the requester side of the reset, and the exact usrsctp mapping-array model. Open: cwnd-full SACK, heartbeat jitter, ASCONF. |
| SDP | 4110 | 85% | Missing: SSRC draws and the pc tables. Open: janus-core does not pass SSRCs yet, so no FID is on the wire from janus-core. |
| RTP and RTCP | 3736 | 90% | Open: the JSON and mutex plumbing of prepare and cleanup (owner decision). Open: the simulcast prepare and cleanup JSON and mutex plumbing (owner decision). Open: the simulcast prepare and cleanup JSON and mutex plumbing (rtp.c 1163-1210), an owner decision. |
| Recording (.mjr) | 528 | 80% | Open: zero-length info header refusal (owner), the recordplay strict header probe (plugin), and the rtp_context copy (needs a Cargo dependency). Open: the zero-length info header and the strict or CPlayout default are owner decisions; the rtp_context copy needs a Cargo dependency. |
| RTP forwarders | 447 | 72% | Missing: the caller-side mutex and thread helper (the crate leaves them to the caller), and no workspace caller uses open_forwarder yet. run_rtcp returns on a recv error, where C keeps looping. Open: AudioBridge must wire the static forwarder send (janus-plugin-audiobridge), and the RTCP socket family differs (std cannot set IPV6_V6ONLY). |
| Text and pcap dumps | 333 | 95% | – |
Full notes for this group
ICE and TURN REST
STUN, candidates, candidate SDP lines, STUN and TURN server validation, pairs, agent, TURN REST logic, RTP header-extension rebuild, media stats, TURN client messages. Missing: STUN probe, TURN REST HTTP, socket I/O, TWCC wiring. The RTP port range falls back to the global range as ice.c 3812-3813 does. The attach loop_index is not wired: in C it picks a static event loop, which this port does not have. Read-only candidate getters (local and remote, insertion order) are added for the admin summaries. The agent has no connection-state enum, so no state getter. The agent derives its connection state (libnice numbering: disconnected, connecting, connected, ready, failed). Gathering is not tracked. A connected counter is bumped once, on the first pair selection, as ice.c pc->connected is set once. A gathering flag is set by a method the core must call on gathering-done, and a failure counter counts consent losses. The core marks gathering done for a full candidate set; a trickle start is not marked yet. C's icefailed is a timer, not a counter. The binary binds a UDP socket for each host candidate of an agent, takes the bound port, receives datagrams into the agent, and sends what the agent returns, polling every 20 ms. A loopback test runs two cores over real UDP sockets: both sides select a pair and key SRTP. Wave 120: inbound RTP from the selected pair is unprotected and handed to the plugin, and the relays it returns are protected with each subscriber's keys and sent from that subscriber's socket. RTCP is unprotected but not yet passed to the plugin. Verified by a two-peer ICE, DTLS and SRTP loopback test that echoes a packet through a plugin and back. Wave 166: role-conflict resolution, the checklist states, switch-role and cancellation, as RFC 8445 (libnice is not in the oracle, so these are not C-verified). Open: the TURN allocation, socket I/O, and the consent interval. Wave 173: the STUN Binding probe's request and reply (ice.c 1078-1210), and the bytes-last-second counter (ice.h 299-310). Open: the TURN REST HTTP exchange (owner decision on the client and TLS), candidate priority values, the consent interval (needs a capture), and the probe socket. Wave 187: the media-state events (notified_lastsec, receiving up and down), the per-second tick with the no-media check, and the media event body are pure functions the caller sends (ice.c 2919-2939, 4439-4468, 4617-4636, 843-881). Open: the TURN HTTP exchange and the candidate priority values (owner decision and a capture); the core must call the tick once per second. Wave 198: the outgoing and incoming keyframe checks need a video packet (ice.c 4922, 2958-2966), so an audio medium clears no NACK store. The nack_optimizations-off case is pinned. Clearing every video medium's store matches janus_cleanup_nack_buffer (ice.c 685-710). Open: TWCC, REMB, slow_link and the shared RTCP counters (owner design). Wave 203: the uplink slow_link check runs per medium (ice.c 4349-4366), gated on the medium having sent a packet, with its own baseline. Open: janus-core does not consume PeerEvent::SlowLink (no plugin callback or slowlink notice, ice.c 1999-2031), the SR packet (s_packets, s_octets, SDES) is not built, the RR recv gate is not modelled, and the TWCC and REMB estimators are owner design. Wave 204: the uplink branch builds the sender report with SDES cname janus, from the medium's out_stats counts and an RTP timestamp from last_ntp_ts (ice.c 4333-4393), and the downlink RR and its slow_link check are gated on a recv flag (ice.c 4369, 4385). janus-core consumes PeerEvent::SlowLink (ice.c 1994-2019). Open: the default medium (no m-line), the janus/src sink wiring, the session recv flag from SDP direction, and the TWCC and REMB estimators. Wave 205: the medium's recv follows the remote m-line direction (sdp.c 202-224, 1570-1590), and janus_ice_media_stopped runs when a renegotiation changes recv (ice.c 4617-4636). The default medium raises no slow_link, as C has no default medium. Open: incoming RTP on a recv-false medium is not gated (ice.c 2676), renegotiation rebuilds PeerMedia state, PT routing to the default medium, and the janus/src sink wiring. Wave 206: incoming RTP on a recv-false medium is neither delivered nor counted (ice.c 2676). A client answer on an existing session re-applies recv in place and announces stopped mediums, instead of returning early. Open: PT-based routing to the default medium (ice.c 2603-2660, SSRC lookup), and the janus/src sink wiring. Wave 207: incoming RTP routes by learned peer SSRC before payload type (ice.c 2603). The default medium learns no peer SSRC and sends no RR, as C has no default medium. Open: mid and rid lookup for unknown SSRCs (ice.c 2604-2660), where C drops the packet and the port falls back to payload type (owner decision), RTX and SDP-declared SSRC seeding (sdp.c 669-689), and simulcast rid. Wave 208: the per-poll loop runs over the m-line mediums only, so the default medium sends no SR, SDES, RR or uplink slow_link (ice.c 4328-4366). The peer SSRC learn runs before the recv gate (ice.c 2618-2625). Each medium's RTX SSRC routes it to its medium (ice.c 1877-1880). Open: the mid-match condition for learning an SSRC (ice.c 2604-2660), and SDP-declared SSRC seeding (sdp.c 669-689). Wave 213: the agent answers RFC 8445 role conflicts with 487 and switches roles when the check list allows, and learns peer-reflexive candidates in handle_datagram_on (janus-core still calls handle_datagram). Open: check retransmission, TURN allocation, consent and priority values (libnice, no capture), and the TURN REST HTTP exchange (owner decision). Wave 219: the first a=ssrc of each m-line seeds the routing table (sdp.c 669-670), the peer SSRC is learned only while unset (ice.c 2721), and each medium's drawn SSRC is its RR sender from creation (ice.c 1865, 4332). Open: FID RTX and simulcast peer SSRCs are not seeded (sdp.c 647-689); the mid-less learn still inserts into ssrc_medium; TWCC media SSRC (ice.c 4193-4198). Wave 220: the recv gate runs before SRTP unprotect (ice.c 2676, 2712-2722), so a recv-off packet does not advance the replay window or teach the peer SSRC. The learned SSRC is no longer inserted into ssrc_medium without a mid or rid match (ice.c 2657-2660). FID RTX peer SSRC is seeded (sdp.c 674-689). The TWCC media SSRC is the audio m-line's peer SSRC (ice.c 4193-4198). Open: the mid and rid match path, simulcast peer SSRCs [1] and [2] (sdp.c 647-672; needs new MediumConfig fields), and the TWCC sender SSRC, which still comes from the caller. Wave 221: a renegotiation carries DTLS and SRTP once ICE has selected a pair, and keeps each kept m-line's NACK store, RTCP context, slow_link baseline and TWCC recorder (janus.c 1700-1800, ice.c 4349-4393). DTLS roles carry only after DTLS has started, which the binary's placeholder replacement needs. Open: the stream-state remap is not pinned separately. Waves 218 and 225: an incoming RTCP compound goes to the m-line that owns its receiver SSRC, then its sender SSRC, and an unknown packet is dropped before the plugin (ice.c 3111-3125, 3282-3285). Local SSRCs are registered at install. Open: the receiver reports use the handle sender SSRC until the medium sends, and simulcast substream vindex. Wave 236: nat_1_1 uses the local address when no public address is known (janus.c 222-230, ice.c 3314-3330, 3486-3523); the TURN REST request username follows ice.c 3648-3662 (turn_rest.rs, 3 tests). Open: the caller wiring in janus-core (CandidateSdpOptions local_ip), the mid and rid and simulcast SSRC paths (new public fields), and libnice behaviour with no C source. Wave 244: nat.stun_server and -S host are read, the system resolver probes the STUN server at startup (exit on failure unless ignore_unreachable_ice_server), the nat_1_1 candidates reach the merged answer, and the HTTP auth rule follows C's NULL handling. Wave 247: general.interface, ufrag/pwd lengths, ice_tcp setting. Open: consent and keepalive timers, ice-lite, full_trickle. Wave 248: every nat., media. and general.* key janus.c reads is applied with C's defaults: ice_lite, full_trickle, ice_nomination, consent freshness, keepalive conncheck, event loops, candidates_timeout and more; REMB with an empty peer SSRC slot is dropped as in C. Open: ICE-TCP pairs (the agent advertises TCP candidates but forms no TCP pair), the SRTP SHA1_32 profile (dimpl 0.9.0). Wave 249: ICE-TCP built end to end and matched to the oracle (parity-media --tcp, 7 scenarios): libnice-order TCP host candidates with address-indexed priorities, RFC 6544 pairing and checks, RFC 4571 framing, prflx tcptype learning, one USE-CANDIDATE nomination like libnice, 400/401 check answers, janus ice_tcp rules. Open: TCP keepalive/reconnect, TCP-SO, TURN over TCP, IPv6 TCP candidates, a stream that closes under a selected pair.
DTLS
dimpl endpoint, role and fingerprint helpers, full handshake through the peer glue with SRTP round trip, failure-path tests, state notification, is_dtls and SCTP routing decisions. The identity is loaded from the configured cert_pem and cert_key. Missing: real socket I/O, SCTP-to-app notify. A self-signed P-256 identity is generated when none is configured (X.509 v1, see PARITY_LOG). The hangup_media call is not gated on an alert, which matches ice.c 4645-4648. Wave 214: check_peer_certificate rejects an expired peer leaf certificate even when self-signed (dtls.c 1030-1045), and dtls_retry_decision applies the 20 s handshake limit, the timer rule and the fatal retransmit hangup (dtls.c 1102-1156). Open: janus-core must call these on DtlsEvent::PeerCertificate and run the retry tick and retransmit counter; the SRTP SHA1_32 profile is blocked on dimpl 0.9.0; state notify on every handshake call is an owner decision. Wave 216: janus-core now fails the handshake on an expired peer certificate (dtls.c 1030-1045) and runs the retry decision on the tick: a 20 s give-up hangs up with DTLS timeout, and a due retransmission is counted and notified (dtls.c 1102-1156, 1134-1137). Open: the SRTP SHA1_32 profile (dimpl 0.9.0), and state notify on every handshake call (owner decision). Wave 248: state-notify 'trying' rule. Open: SHA1_32 profile (dimpl 0.9.0).
SCTP and data channels
Codec, association, RE-CONFIG, DCEP, congestion control and fast retransmit, channel table, stream allocation, open, close, legacy open decisions and ack rules. Stream reset: the association reports completed resets, and the core applies them to the channel table and sends the reset it asks for. Missing: usrsctp I/O, checksum order (unverified). Wave 136: the DTLS endpoint carries application data (janus-dtls ApplicationData and send_application_data), and each PeerSession with an application m-line runs an SCTP association over it: the client connects when DTLS connects, and the packets go out as DTLS records. A two-peer test sends a message on stream 3 with PPID 51 and receives it on the other side. Wave 137: DCEP is handled on the association: an OPEN gets the channel and an ACK, an ACK opens the channel, and a data message on an open channel goes to the plugin with its label and subprotocol. The plugin's relayed data goes out on the open channel with its label, or on the first open one (text PPID 51, binary 53). A channel the peer closes is closed here too. Each sent reset is reported to the channel table, so the stream leaves its buffer. Plugin data uses the route of the channel table: a label with no open channel opens one, its OPEN goes out first, and the data follows on its stream. Opens and ACKs the association did not accept are retried in poll. A peer reset leaves the channel CLOSING. Wave 166: FORWARD TSN receive (RFC 3758 3.6), the ERROR chunk and stale cookie handling (RFC 4960 5.2.6), originated HEARTBEAT, and SSN reset requests answered by our own request. Open: the TSN reset, the add-streams request, fast retransmit and congestion control, and the CRC32c byte order (needs a usrsctp capture). Wave 171: congestion control and fast retransmit are on main; three association tests pin RFC 4960 7 and 6.3.3. Wave 175: add outgoing streams, RFC 6525 sections 4.5 and 4.6 with 5.2.5 and 5.2.6, driven by the association. Open: the TSN reset and the incoming SSN reset requests. Waves 181: the SSN and TSN reset request wire form is pinned by a round-trip test, and the incoming SSN reset is tested. A peer TSN reset is still answered Denied: the Sender's Next TSN is only in the response (RFC 6525 4.4), so it needs a usrsctp capture to port. Wave 197: the outgoing and incoming SSN reset requests were already implemented; five tests pin the denial, in-progress, deferral and empty-list cases. The TSN reset is still answered Denied and needs a usrsctp capture, since the oracle never requests it. Set to 89 because the TSN reset remains open. Wave 241: the CRC32c is stored little-endian and checked against usrsctp 0.9.5.0 in the oracle image (match, 2 tests). Open: usrsctp performs a peer SSN/TSN reset and answers Performed; janus-sctp answers Denied. Wave 243: a peer SSN/TSN Reset Request is answered Performed and rebases the TSN map and the local next TSN, reproducing the captured usrsctp response bytes (receiver-next TSN is a fit to 15 captured points; the lazily slid mapping array is not modelled). PPIDs 56 and 57 are ignored; 52 and 54 are buffered and a later 51 or 53 delivers only the buffer (sctp.c 1146-1172). An empty send opens no channel and sends nothing (usrsctp refuses it with EINVAL). Open: the requester side of the reset, and the exact usrsctp mapping-array model. Wave 247: crossing INITs resolved per RFC 4960 5.2; the Rust gateway sends its INIT first like C; the media-harness waiver is removed. Wave 249: raw-packet probe (parity-media --sctp, 17 scripts) matches the oracle: delayed SACK engine, DCEP ACK before SACK, a_rwnd and TSN map window, usrsctp RTO and retransmit, reset flow order and responses, ABORT/SHUTDOWN COMPLETE with the T bit, heartbeat info, sender-dry to data_ready. Open: cwnd-full SACK, heartbeat jitter, ASCONF.
SDP
Lossless model, helpers, offer and answer generator, remote and local process helpers, local attribute removal, RTCP reset decision, mid choice. Missing: SSRC draws and the pc tables. Wave 167: JanusSdp::anonymize (sdp.c 1317-1452) and the payload-type removal (sdp-utils.c 680-709). Wave 184: the answer keeps RTX payload types with apt, and the FID group and ssrc lines are opt-in when the caller supplies SSRCs (sdp.c 579-605, 1593-1605, 1678-1706). Open: janus-core does not pass SSRCs yet, so no FID is on the wire from janus-core.
RTP and RTCP
Packet layer, contexts, simulcast and SVC selectors with prepare and cleanup, VP8 descriptor rewrite, stats, RED, AV1 layers allocation, REMB relay branch. Missing: TWCC wiring. Wave 166: the codec name and payload-type tables and the a=extmap lookups (rtp.c 82-160, 993-1147), checked with the simulcast prepare and cleanup and the AV1 layers parse against C-run fixtures. Open: the JSON and mutex plumbing of prepare and cleanup (owner decision). Wave 173: the SVC context and the AV1 Dependency Descriptor context match rtp.c 1442-1796 in a C-run differential over 80 cases, and the RTCP context walker matches rtcp.c 52-794 over 30 cases. Open: the simulcast prepare and cleanup JSON and mutex plumbing (owner decision). Wave 213: janus_srtp_error_str is ported with libsrtp 2 and 1.5 names (rtp.c 987-1007). Open: the simulcast prepare and cleanup JSON and mutex plumbing (rtp.c 1163-1210), an owner decision. Wave 247: simulcast prepare/cleanup JSON plumbing; TWCC builder C-run differential.
Recording (.mjr)
Writer, reader, folder protection, path rules. Wave 213: with no filename, the random name goes to the working directory and no folder is made (record.c 127-147). Open: zero-length info header refusal (owner), the recordplay strict header probe (plugin), and the rtp_context copy (needs a Cargo dependency). Wave 231: the codec table, description, extmap, timestamp and length boundaries are pinned to record.c; the codec header reads the RED or field for audio only (janus_recordplay.c 769-771). Open: the zero-length info header and the strict or CPlayout default are owner decisions; the rtp_context copy needs a Cargo dependency. Wave 242: the frame reader skips a zero-length info header as C does (janus_recordplay.c 708, janus-pp-rec.c 483); the codec lookup keeps refusing, as C returns NULL there. Wave 247: writer checked against record.c over 189 scripts.
RTP forwarders
Config, rewrite, latch packet, RTCP add logic, forwarder registry. UDP socket I/O on tokio: each RTP copy is sent per packet, and RTCP receive feeds the existing feedback logic. Wave 103: RTP goes out from the caller's shared socket with sendto semantics, as rtpfwd.c does with the publisher's udp_sock (open_forwarder_shared); RTCP stays per forwarder. Tested over loopback. Missing: the caller-side mutex and thread helper (the crate leaves them to the caller), and no workspace caller uses open_forwarder yet. run_rtcp returns on a recv error, where C keeps looping. Wave 170: the SRTP key base64 decoder matches GLib's g_base64_decode (rtpfwd.c 176-180). Wave 174: the base64 key decoding is checked against GLib on ten cases (rtpfwd.c 176-180). Wave 177: rtpfwd changes with tests (rtpfwd.c). Wave 214: the RTCP receive loop keeps going after a receive error, as rtpfwd.c 102-113 does. Open: AudioBridge must wire the static forwarder send (janus-plugin-audiobridge), and the RTCP socket family differs (std cannot set IPV6_V6ONLY). Wave 247: dual-stack RTCP socket.
Text and pcap dumps
pcap writer, text-line formatter, dump naming, truncation and protected-folder rules. Wave 103: TextDump creates the folder (mkdir 0755 with parents), applies the protected check with realpath semantics before opening, truncates on open, and writes the pcap header only for pcap. No CLI: text2pcap.c has none, and its only surface is the admin start_pcap and start_text2pcap requests (janus.c 189-193, 2999-3030), whose wiring is not checked here. Text lines take a caller-supplied timestamp, since C uses localtime. The C comment at text2pcap.c:23 says appends, but the code opens with wb and truncates (PARITY_LOG Wave 103). Wave 247: record/text2pcap dump checked against C over 189 scripts.
Plugins
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Plugin: EchoTest | 1463 | 99% | Open: E2EE refused, rid SSRC learning for no-codec video. Open: the async configure parity capture (Docker). Open: the async configure event (Docker capture). |
| Plugin: VideoCall | 1853 | 98% | Open: send_pli and close_pc in CoreCallbacks are no-ops, so PLI and PeerConnection close do not reach the peer. Open: the hangup verb and admin hangup send no event. Open: the list-order deviation (row 107). |
| Plugin: VideoRoom | 14309 | 99% | Open: audio stereo, fec and dtx flags, and the video profile and extmap fields. Open: data-channel SCTP and DCEP, the real SDP offer inspector, and the persistence. Open: the file-backed store adapter over janus-config (needs a Cargo.toml dependency), the SDP offer inspector (owner), and subscribing to dummy streams. |
| Plugin: Streaming | 10928 | 99% | Open: the relay return counts include unserved viewers, a documentation and semantics issue. Open: RTSP, H.264 sprop injection, helper threads, saving to config. Open: rtsps (needs TLS), libcurl redirects/NTLM, an RTSP media scenario in the harness. |
| Plugin: RecordPlay | 3309 | 97% | Open: the simulcast record path (janus_recordplay.c 1493-1520, 2074-2100), local time (owner decision). Open: local time (owner decision); the janus-rtp identification step is private, so the plugin copies it. Open: the t-without-c probe (janus-record), the short data frame, the playout driver, and the simulcast record details already listed. |
| Plugin: TextRoom | 3290 | 98% | Open: HTTP send and config save. Open: https and credentials (owner decision). Open: https (TLS, owner decision). |
| Plugin: AudioBridge | 10052 | 99% | Open: static forwarder SRTP send, room socket creation (the mixer thread is not ported), and .jcfg permanent rooms. Open: the mixer clock thread (the caller's), the rtp_forward request and static config wiring, and SRTP group forwarders. Open: .jcfg reading (needs janus-config), SRTP group sending, lock_rtp_forward, and ipv6_disabled. |
| Plugin: SIP | 9128 | 99% | Open: rsipstack ends the call dialog when it cannot answer a challenge (needs an rsipstack change, an owner decision), transport errors are dropped (C reports 408 or 503), and the helper-session master credential (7138-7140). Open: paths that need a live SIP peer. Open: Proxy-Authorization on BYE, keep-alive OPTIONS after REGISTER, terminating NOTIFY for a pending REFER. |
| Plugin: NoSIP | 3587 | 96% | Open: VERB, HUGE and DBG lines, and the relay-task lines (3189-3483). Open: the allocation ERR lines need a reason from MediaSeam, and the relay resolve ERR lines need IPv6-capable resolution. |
| Plugin: Duktape (Boa) | 3022 | 99% | Open: the substream and incoming-media hook error logs. Open: the setup, session, hangup, data-ready, slow-link and timer hooks still drop their errors. Open: the other load errors are logged but not individually pinned. |
| Plugin: Lua | 2632 | 94% | Open: a non-text answer is asked again, not cached, because the trait cannot carry NULL; the number-to-text formatting differs from Lua for some floats. Open: the non-text getter answer and the relayTextData non-UTF-8 payload, both owner decisions. Open: the number-to-text edge cases that the wave reports list as known differences. |
Full notes for this group
Plugin: EchoTest
Logic, configure parity check, SDP offer and answer. DTLS and ICE start seamed out; simulcast and SVC jsep refused. Wave 147: recording start and stop through the core hook, one .mjr per media, close on hangup and destroy; query codec names and recording names. Open: RED mark (needs an opusred field on RecordingRequest in janus-core), e2ee mark, temp-extension name in query. Wave 148: the RED mark is written on audio recordings (RecordingRequest.opusred_pt) as janus_recorder_opusred does. Open: e2ee mark, temp-extension name in query. Wave 150: simulcast and SVC setup from the JSEP object, layer targets, and the relay through the janus-rtp selectors. Open: E2EE refused, rid SSRC learning for no-codec video. Wave 155: the rid SSRC is learned while the codec is NONE, with the relay gate unchanged (janus_echotest.c 639, rtp.c 1216-1258). Wave 159: e2ee is accepted as C does: the answer, the query and the audio and video recordings carry it, and hangup clears it (janus_echotest.c 1005-1007, 1306-1307, 1355-1387). Wave 164: the recording file names follow record.c 187-201 and 496-504, through janus-record's public temp extension. Wave 169: the codec names go through janus-rtp's codec table (rtp.c 993-1147); the duplicate table is removed. Open: the async configure parity capture (Docker). Wave 177 audit: the ack ordering and the partial apply on a failed offer match C and are pinned by tests. Open: the async configure event (Docker capture).
Plugin: VideoCall
Registry, call state, relays, offer and answer, set answer, simulcast defaults, substream selection. The simulcast relay is gated per viewer until a keyframe, with SSRCs from the first simulcast item. The gated path rewrites sequence and timestamp per viewer and applies the temporal layer rules. The VP8 descriptor is updated per viewer, and the temporal rules for VP8, VP9 and AV1 live in janus-rtp. Its answers go through the core's merge. Missing: the ICE agent creation on the answer path, and the DTLS start. Wave 149: peer calls resolve through core session_of by gateway handle; the stand-in peer session is removed. Open: send_pli and close_pc in CoreCallbacks are no-ops, so PLI and PeerConnection close do not reach the peer. Wave 150: send_pli reaches every video medium and close_pc is deferred and flushed from tick with a Close PC hangup, both through the core. Open: the hangup verb and admin hangup send no event. Wave 165: the set-with-answer path applies the answer's media flags and the codec-none rule (janus_videocall.c 1571-1592). Open: the list-order deviation (row 107). Wave 170: recording names are cut to C's 255-byte buffer (janus_videocall.c 1627-1710). Wave 178: general.events is read from the config file and gates the handler events (janus_videocall.c 467-498). Wave 248: videocall-requests scenario; no admin hook gives 490.
Plugin: VideoRoom
Rooms, participants, per-subscriber rewrite, simulcast and SVC, VP8 descriptor rewrite, switch with legacy feed, renegotiation and pending restart, subscriber answer, relay gate on ready, recording state, data-channel routing. The recorder writes .mjr files per published stream through janus-record, and an uncreatable folder leaves the room running. The recorder covers simulcast (top layer) and a PLI on recording start with a peer. RTP forwarders (rtp_forward, the legacy rtp_forward form, stop_rtp_forward, listforwarders) cover audio, video, simulcast and data. publish_remotely, unpublish_remotely and listremotes forward audio and video to a remote host with the remote SSRC. Remote publishers (add, update, remove) place packets by SSRC, decrypt SRTP with their own keys, latch the RTCP sender and send keyframe requests back to it. Configure with restart or update negotiates again, postponing the offer while an answer is outstanding. Removing a remote publisher queues its sockets for release, and the socket layer closes them. Data messages of a publisher reach each subscriber through the core, with the publisher id as the label (Wave 140). Missing: data frames recorded, data streams from a remote publisher, subscriber events for remote publishers, the data remotization form. Wave 148: remote publisher data streams relay through the local publisher path, data_mindex selects the stream, data forwarders and recording run for active unmuted streams, publish_remotely sends data as RTP. Open: rtp_forward survives unpublish and resumes on re-publish (C frees it with the stream); data SCTP seams; live oracle check. Wave 149: forwarders are freed with their streams on unpublish and re-added on re-publish; listforwarders and listremotes read the C registry. Open: simulcast substream forwarders of publish_remotely, SRTP for remotization, the remote host text echo. Wave 150: publish_remotely recipients get SRTP keying (audio and video, not data) and it is reused on re-add. Open: simulcast substream forwarders of publish_remotely, the remotization PLI stream lookup by SSRC (C 13607-13640). Wave 152: the remotization PLI maps to its stream by media SSRC (janus_videoroom.c 13596-13687), with no fallback to the receiving forwarder. Open: simulcast substream forwarders of remotization (janus_videoroom.c 7395-7410). Wave 153: simulcast substream forwarders for publish_remotely and the re-add (janus_videoroom.c 7395-7410, 13216-13231), and copy_packet routes the real substream. Open: the receiving remote room drops substreams 1 and 2 for simulcast remote streams (C 13996-14000). Wave 154: the receiving remote room accepts substreams 1 and 2 of a simulcast stream (janus_videoroom.c 13996-13999). Open: the remote_ssrc_offset, the codec check for simulcast, and the lookup of an update by mid (C 8210-8235). Wave 155: the remote SSRC offset from the CSPRNG, the simulcast codec check, the update lookup by mid, and the decimal mindex as the stream mid (janus_videoroom.c 7871, 7989-8011, 8210-8243, 14099). Open: the audio codec check (7960-7982) and the remote update notification (8402). Wave 156: the audio codec check on add and update, and the updated notification to the room (janus_videoroom.c 7960-7973, 8322-8335, 8402). Open: audio stereo, fec and dtx flags, and the video profile and extmap fields. Wave 168: the subscriber renegotiation offer carries the Opus fmtp, msid and the extmaps in C's order (janus_videoroom.c 3648-3697). Wave 171: talk detection and the talk flags, the offer extmap ids, and the slow_link event (janus_videoroom.c 8795-8843, 4315-4330, 12914-12928). Open: data-channel SCTP and DCEP, the real SDP offer inspector, and the persistence. Wave 189: room persistence through a RoomStore seam, and the dummy publisher (janus_videoroom.c 3725-4136, 5252-5290, 3132-3275). Open: the file-backed store adapter over janus-config (needs a Cargo.toml dependency), the SDP offer inspector (owner), and subscribing to dummy streams. Set back to 99 because these remain open.
Plugin: Streaming
Mountpoints, viewers, RTSP status and SDP parsers, file-source validation and clock arithmetic, Ogg and OpusHead parsing, PLI and REMB builders, keyframe buffer, viewer relay with plain, SVC and simulcast selection, RID table and scan, VP8 descriptor save and restore, VP9 and AV1 keyframe rules, extension copies, layer events. Missing: per-viewer state, AV1 Dependency Descriptor rules, file I/O, libcurl, threads, the core push. Each viewer owns a ViewerState, synced at watch, setup, start, pause and configure, and relayed through relay_viewer_packet. The C switch unpause and the VP8 descriptor save and restore are wired; the PLI on switch is queued as a request for the caller to build and send, and the simulcast relay drops header-only packets. Wave 148: VP9 temporal rule in the simulcast selector (rtp.c 1371-1400). Open: AV1 Dependency Descriptor temporal rule (needs a janus-rtp dependency), VP8 descriptor rewrite, the send path that computes temporal facts. Wave 149: VP8 simulcast descriptor rewrite with a per-viewer context. AV1 stays NoRule, as C streaming never passes a descriptor. Open: the VP8 context reset on watch and offer, and the send loop. Wave 151: viewer push, close and notify resolve through session_of, and a viewer the core does not serve is skipped. Open: relayed RTP, RTCP and data have no core relay call yet (the send path is not in the crate). Wave 152: the viewer send loop (plain, SVC and simulcast RTP, RTCP) is a pure plan delivered through the core with session_of. Open: RTP data-channel relay, keyframe burst buffering, and a caller for the queued PLI requests. Wave 153 (partial): the viewer data relay with the buffermsg replay, and a PLI caller that drains queued keyframe requests from the source SSRC. Open: PLI bytes go through the source RTCP socket, which is not in the crate, and the data relay needs a RelayPacket::Data variant in janus-core. Wave 154: the data relay and its buffered replay go through deliver_relays (RelayPacket::Data). Open: the PLI bytes through the source RTCP socket, and the viewer PLI and FIR relay. Wave 158: the viewer PLI and FIR relay as queued keyframe requests, gated as janus_streaming.c 5976-5993, and the keyframe burst (5920-5933, 9680-9717). Open: the PLI bytes on the source RTCP socket. Wave 160: the viewer's PLI is sent on the source's latched RTCP address (janus_streaming.c 1936-1961, 10404-10413). A request with no latched address stays queued. Wave 164: the simulcast and VP8 contexts reset on watch and offer, the watch keys and fallback reach the selector, and viewer REMB lowers the source bitrate and is sent once a second (janus_streaming.c 6478-6530, 6865-6868, 5999-6004, 1969-1992, 9975-9984). Open: the streaming selector duplicates janus-rtp simulcast.rs (needs a reconcile decision). Wave 169: the simulcast selector is checked against janus-rtp in a 20000-step differential test, and the VP9 temporal rule calls janus-rtp's implementation (rtp.c 1371-1400). Wave 177 audit: the is_private default, the recording name, the PLI and REMB sends and the layer events are pinned by tests. Open: the core peer resolution for fan-out, and the duplicate selector refactor. Wave 190: no stand-in PluginSession remains in production; every viewer and relay call resolves through session_of, and an unserved handle gets no call (janus.c 3710-3714, 4273-4302, 4417-4427). Open: the relay return counts include unserved viewers, a documentation and semantics issue. Wave 209: the relay functions return the plan size, counted before session_of; C's relay is void, so the count has no C counterpart and no caller reads it. Delivery is unchanged; one test pins the count with a stopped viewer. Wave 226: the duplicate simulcast selector is now the one janus-rtp rule (SimulcastContext::process_identified), with the streaming tests unchanged. The relay count doc (PARITY_LOG 2480) stays open. Wave 236: the deprecated audio, video and data RTP create form (audioport, videoport, dataport) is ported with its per-kind validation and VP9-only SVC rule, 11 pinning tests. Wave 247: RTP/RTCP/data sockets, source relay, recording, file sources, PLI/REMB to the source; streaming-watch matches the oracle. Open: RTSP, H.264 sprop injection, helper threads, saving to config. Wave 248: RTSP client (DESCRIBE, SETUP, PLAY, OPTIONS keepalive, TEARDOWN, Basic and Digest auth, rtsp_quirk, reconnect) with request text checked against the oracle; H.264 sprop injection; helper-thread assignment; saving to the config file; keyframe burst leaves inside setup_media. Open: rtsps (needs TLS), libcurl redirects/NTLM, an RTSP media scenario in the harness.
Plugin: RecordPlay
Record, play, playback relays, offer and answer, RED demux, videoprofile. Missing: .mjr file I/O, playout thread, ICE and DTLS. Reads .mjr recordings in file order through the janus-record reader, with the recorded times for the caller's playout. The playout runs as a tokio task that delivers frames at their recorded times, and stops at the end of the file or when told to. Registered in the binary from janus.plugin.recordplay.jcfg general.path; checked live. Wave 147: playout offer takes fmtp and extension ids from the recording; record headers carry f and x; scanned codecs use the preferred list; the renegotiation answer comes from the open recorders. Open: janus-record InfoHeader requires s and u (other crate), data m-line. Wave 148: janus-record gained a t+c-only codec header parse (parse_codec_header) matching janus_recordplay_parse_codec; the playout probe in recording.rs still uses the strict reader, so the switch is the next step. Wave 149: the probe reads t and c only (janus_recordplay_parse_codec), frame reads stay strict. The 1500-byte info header and 255-byte fmtp limits are enforced. Open: a header with t but no c keeps no e2ee or fmtp in C; data m-line recording. Wave 154: data-channel recording and playback (the application m-line, incoming data written to the data recorder, hangup, pause and resume). Open: the playback of data messages was not checked against the oracle. Wave 169: invalid audio-level and video-orientation extension ids log the C warning before the recording event (janus_recordplay.c 1936-1957). Open: the t-without-c probe (needs a janus-record change), the short data frame, the playout task driver, and the C playout read policy (owner decision). Wave 177 audit: the record and play offers, RED, data and data-only playout rows are stale and pinned by tests. Open: the simulcast record path (janus_recordplay.c 1493-1520, 2074-2100), local time (owner decision). Wave 179: the simulcast record path (janus_recordplay.c 1487-1520), the jsep setup (2074-2100) and the resets (1088-1091, 1606-1607, 1697). Open: local time (owner decision); the janus-rtp identification step is private, so the plugin copies it. Wave 191: the .nfo date is the host's local civil time from localtime_r, with UTC as the fallback (janus_recordplay.c 1959-1963). Open: the t-without-c probe (janus-record), the short data frame, the playout driver, and the simulcast record details already listed. Wave 210: the simulcast identification copy is removed; janus-rtp exposes SimulcastContext::identify, and process_rtp uses it (rtp.c 1213-1262). Pinned by 2 janus-rtp tests and 1 recordplay test. Wave 222: the header probe keeps the fmtp and the e flag when there is no c (janus_recordplay.c 754-768), and playback reads with the CPlayout skip rules (2660-2748). A header with no c yields no codec, as C does; the task's premise was wrong. Wave 248: recordplay-requests and recordplay-admin-key scenarios; GLib 2.74 int64 hash order.
Plugin: TextRoom
All verbs and config. HTTP send and cross-session delivery missing. Wave 149: room deliveries go out as relay_data on the target session (janus_textroom.c 1651), so cross-session messages are no longer dropped. Open: HTTP send and config save. Wave 150: HTTP bodies and config records pinned by ten tests to janus_textroom.c key order. The binary must drain the outbox and apply config changes. Wave 153: the binary drains the TextRoom outbox after each call, POSTs the HTTP requests in order, and applies config changes with get-or-create plus per-key upsert, reading the config at init. Wave 167: the janus binary installs the ConfigSaver, so config changes are written during the call and the reply reports the saver's result (janus_textroom.c 2701-2744, 2874-2912, 2979-3039). Open: https and credentials (owner decision). Wave 170: URL credentials are sent as Basic auth (janus_textroom.c 1713-1732). Open: https (TLS, owner decision). Wave 248: textroom-requests, textroom-admin-key and textroom-string-ids scenarios.
Plugin: AudioBridge
Room and participant logic, join SDP negotiation (G.711), per-tick G.711 mix with volume and recorder at room rate, group submixes and forwarder decisions, forwarder config, jitter buffer with adaptive delay, limiter, spatial pan. Opus gated on G3: the vendored decoder (crates/janus-opus) passes the stereo vectors; mono reference question open. File I/O, encoding, mixer thread missing. Wave 147: mix, limiter and recording run at room rate in C's order (spatial 16/24/48 kHz layout and the 12 kHz room fixed). Open: announcement mixing (needs Ogg/Opus file decode), the upsample stale-slot question (needs an oracle capture), recording file I/O. Wave 152: announcement playback from Ogg Opus files is mixed before the limiter (janus_audiobridge.c 8880-8985), with the started and stopped events. Open: libogg page-handling parity is not verified. Wave 154: the room recording is a WAV file with the 44-byte header, a size refresh and a fix-up on close (janus_audiobridge.c 8447-8552, 9009-9024). The per-participant .mjr recordings are a separate C path, still open. Wave 155: per-participant .mjr recordings through janus-record under C's file names, with the audio-level extmap, written at join, leave, suspend, resume and hangup (janus_audiobridge.c 6392-6478, 4048-4110). Wave 168: the Opus join answers with C's fmtp, each Opus participant gets libopus at the room rate, and the per-tick mixer decodes with FEC off and conceals lost frames (janus_audiobridge.c 8271-8364, 7025-7070, 9338-9417). Open: the FEC decode path and the Opus encode details, as the wave reports. Wave 171: the group RTP forwarder encode decisions, G.711 and Opus, with the per-group cache (janus_audiobridge.c 9123-9238). Open: the Opus bandwidth and loss setters (janus-opus), the join order (owner), and the capture-dependent upsample and limiter items. Wave 176: resetdecoder and the audit fixes (janus_audiobridge.c). Several open rows were closed in the audit. Wave 192: the group forwarder RTP header and sequence steps (janus_audiobridge.c 9231-9236), the static forwarder SRTP length check and stream ID helpers (rtpfwd.c 155-188, 161-176). Open: the UDP send, the socket and encoder creation, and the .jcfg permanent rooms (needs a janus-config dependency). Wave 198: group forwarder packets are sent over UDP, with the payload type and SSRC overrides (janus_audiobridge.c 9238, rtpfwd.c 365-376). Loopback tests pin the wire bytes. Deviation: one IPv4 socket plus an optional IPv6 socket, since std cannot set dual-stack without a dependency. Open: static forwarder SRTP send, room socket creation (the mixer thread is not ported), and .jcfg permanent rooms. Wave 217: each room holds its group forwarder UDP sender and encoder, bound at the first add as C does (janus_audiobridge.c 2516-2525, 5150-5160), and each mix tick sends the group packets through send_tick (9123-9142, 9238). Open: the mixer clock thread (the caller's), the rtp_forward request and static config wiring, and SRTP group forwarders. Wave 218: participant encoders set max bandwidth, expected loss, bitrate and complexity as C does (janus_audiobridge.c 7047-7063, 7464); the rtp_forward request is ported with C's reply shape (5150-5160), and a static add is exposed. Open: .jcfg reading (needs janus-config), SRTP group sending, lock_rtp_forward, and ipv6_disabled.
Plugin: SIP
Call and registration state, media relays, SipStack seam, forwarders, recording, DTMF, REFER state, SDES rules, custom contact parameters and helper/master decisions as pure functions. Wire stack is gate G4. Wave 1 (Wave 143): the rsipstack bridge is on main, and REGISTER and unREGISTER run end to end over UDP with one digest retry, checked against a fake registrar. INVITE, dialog, REFER, INFO, MESSAGE and media legs are not on main yet. Waves 2 and 3 (on main): outgoing and inbound INVITE with ACK and answer, BYE and re-INVITE on attached dialogs, REFER and SUBSCRIBE, INFO and MESSAGE, media legs with RTP and RTCP sockets, config and TCP for INVITE and SUBSCRIBE. Peer re-INVITEs are answered on both directions (autoaccept as C, or through updatingcall). Wave 146: the peer re-INVITE SDP directions (C janus_sip_sdp_process, 7836-7890) run before autoaccept and the app-answered path, and incall is restored after the 200 answering a peer update. Missing: 488 on an invalid or media-less peer SDP, remote addresses, ports and SRTP from that SDP, incall_reinviting for our own re-INVITEs, nothing is compared with the C oracle yet. Wave 147: our own re-INVITE sets incall_reinviting and the final response restores incall (ReinviteFinal). A peer re-INVITE with an invalid or media-less SDP is answered 488. Still open: setup-time direction flags, ports and SRTP from the setup SDP; end-to-end stack coverage of 488 and ReinviteFinal. Wave 148: setup-time SDP sets directions, remote RTP ports, require_srtp and the SDES key and tag from the incoming offer and the answer. Open: dtmf_pt and codec payload types, rtcp-fb pli, the mandatory-SRTP hangup, srtp_create and protect, teardown media reset, 488 on the setup offer. Wave 149: dtmf_pt on setup and re-INVITE, audio_pt, opusred_pt and video_pt on the answer, video_pli_supported from rtcp-fb pli, remote port on re-INVITE, media reset at teardown. Open: re-INVITE SRTP key and require_srtp, the RTCP port, consumers of the payload types (they need RTP and RTCP I/O). Wave 150: peer re-INVITE SRTP. The a=crypto loop runs with the answer flag, require_srtp is sticky, and a kind's remote key is not overwritten. Open: srtp_create failure path, the 2xx to our own re-INVITE processed as an answer (janus_sip.c 7363 with update). Wave 151: the 2xx to our own update re-INVITE is applied as the setup answer, the state returns to incall, and an accepted event carries the answer. Open: the end-to-end 2xx body path has no test in janus-sip-stack. Wave 154: an end-to-end test of the 2xx to our re-INVITE. It found and fixed the ACK taking its Route headers from the setup INVITE; the ACK now uses the dialog's route set (RFC 3261 12.2.1.1). Wave 155: codec names from the answers, the PLI to the SIP peer on the video leg, and the accept answer payload types (janus_sip.c 5085-5092, 6136, 9030-9066). Open: the recorder RED mark, which needs a janus-core recording seam change (janus_sip.c 5765, 5826). Wave 156: the RED audio recording request (janus_sip.c 5761-5827). Open: the recorder seam has no pause or resume on PluginCallbacks, and no PeerAudio or PeerVideo recording kind (janus_sip.c 5767, 5796, 8735, 8826). Wave 157: the recording action pauses and resumes the own recorders through the core callbacks (janus_sip.c 5863-5878). Hold and unhold do not pause, as in C. Open: peer audio and video recording kinds (owner decision), and start and stop recording from SIP. Wave 158: start and stop of the own audio and video recorders through the core callbacks, with C's codec, RED mark and file names (janus_sip.c 5806-5847, 3648-3708, 5881). Open: the video-orientation extmap on the own video recorder (needs a RecordingRequest field), peer audio and video recorders (owner decision), and the flags left set after a refused start. Wave 159: setup offers are refused with 488 when they do not parse or leave no media or remote IP (janus_sip.c 6563-6631); the RTP sequence reset flags on resume; the own video orientation extmap (5853-5855). Open: the sequence reset has no consumer until the SIP relay is ported (8720, 8811); the SRTP create has no production implementation, because plugins/sip has no janus-srtp dependency. Wave 160: the peer's SRTP is unprotected with the inbound context built from the remote SDES key; the relay header update and the peer SSRC write run on relayed RTP (janus_sip.c 1726-1809, 8686-8851). Open: outbound protect with the local key and the offer a=crypto (janus_sip.c 1709, 3475-3556, 9052); peer forwarders get ciphertext without the header rewrite under SRTP (8725-8730). Wave 161: peer forwarders are given the unprotected, header-rewritten packet from one relay entry point, in C's order (janus_sip.c 8706-8734, 8815-8825). The local SRTP key and outbound context are built as janus_sip_srtp_set_local does (1642-1720). Open: the stack does not call relay_sip_rtp yet, so forwarders are still not fed in production; outbound protect in janus-sip-stack; janus_sip_sdp_manipulate (8012-8066) has no caller for the local key. Wave 162: the stack sends the forwarder packets from relay_sip_rtp, protected per stream when SRTP is set, and protects browser RTP and RTCP with the outbound context of each kind (janus_sip.c 8725-8737, 3475-3556, 3595, 3625, 9052). The offer manipulate writes the a=crypto line with the local key (janus_sip.c 8006-8131). Open: set_peer still refuses SAVP and a=crypto, so an SRTP call never reaches the relay; no caller drives the browser-side forwarders (MediaLegs::send_forwarded); call, accept and update do not call sdp_manipulate yet, because local port allocation and sdp_ip are not ported. Wave 163: the peer SRTP refusal is lifted, peer SRTCP is unprotected with the remote key, and the offer manipulate is wired into call, accept and update with allocated local ports and sdp_ip (janus_sip.c 7919-7950, 8752-8870, 8135-8163, 4664-4686, 5069-5078, 5284-5296). Open: update's port allocation needs the audio and video added flags; no socket-level test sends a protected packet end to end. Wave 165: an update binds only the kinds it adds and keeps the open ports (janus_sip.c 5254-5296, 8178-8356), with a socket-level SRTP test through the bridge. Open: accept's progress path passes the SDP-carried flags, not the sticky ones (5069-5078). Wave 173: the helper session branch (register as helper, helper unregister, master and helper destroy links and query keys). Open: the owner decisions on the recording kinds. Wave 187: incoming header prefixes on INFO, MESSAGE and incomingcall events, and REGISTER custom headers (janus_sip.c 1950-1972, 3909-3927, 4140-4292). Open: the subscribe results, the headers key on other events, the owner decisions on recording kinds and the update-answer expectation. Set back to 99 because owner items remain. Wave 188: subscribe_succeeded and subscribe_failed through the bridge (janus_sip.c 7671-7790, 1467-1473); a 4xx-6xx removes the subscription before reporting. Open: the owner items. Wave 196: REFER decline (janus_sip.c 5346-5396), attach_call with Referred-By and Refer-To (4799-4816, 4897-4898), progress NOTIFYs for 180, 183, 200 and a final failure (7210-7224), and NOTIFY on SUBSCRIBE dialogs (6910-6953) are wired, with 10 tests. Open: 1xx codes other than 180 and 183, the phrase fallback for unlisted codes, the decline NOTIFY once the owner dialog is gone, the REFER answer (nua_r_refer) and the 401 and 407 retry on transfers. Needs capture: 100, 181, 182 and the no-response paths. Set to 99 because these and the owner items remain open. Wave 197: the REFER final answer reaches the plugin (StackEvent::ReferFinal). A final 300 or more pushes transfer_failed with no call_id; a 2xx pushes nothing (janus_sip.c 7111-7160). The 401 and 407 retry is rsipstack's one-shot digest retry, pinned by a loopback test. Open: rsipstack ends the call dialog when it cannot answer a challenge (needs an rsipstack change, an owner decision), transport errors are dropped (C reports 408 or 503), and the helper-session master credential (7138-7140). Set to 99 because these and the owner items remain open. Wave 248: sip-requests (495 exchanges) and plugins-admin-api; refresh without a stack gives 499; guest and send_register:false get a stack; admin_key, events and register_ttl read. Open: paths that need a live SIP peer. Wave 249: live-peer harness parity/sip-peer (xtask parity-sip): sofia's header set and order, rtp_port_range honoured, proceeding event, hangup phrases, OPTIONS 200, re-INVITE answers. Open: Proxy-Authorization on BYE, keep-alive OPTIONS after REGISTER, terminating NOTIFY for a pending REFER.
Plugin: NoSIP
Session logic, verbs generate, process, hangup, keyframe, recording, forwarders, SRTP protect, NULL-message path. Forwarder sockets and per-socket relay tasks on tokio. Copies are sent with an async send, which waits for the socket to be writable. Tested over loopback. The forwarder socket is wired through a seam that refuses outside a tokio runtime, and the RTP copies carry the rewritten payload type and SSRC. Registered in the binary behind plugin-nosip, from janus.plugin.nosip.jcfg general. The SDP seam uses janus-sdp for the barebone SDP. The media seam opens forwarder sockets, creates plain forwarders, and draws unique ids from the core CSPRNG. DNS host names resolve through the system resolver, and RTP port pairs come from the configured range. SRTP forwarders are protected with the forwarder key. RTP is sent to the legacy peer's address, learned from the accepted SDP. WebRTC-side RTCP is sent to the legacy peer through the incoming_rtcp hook, protected when the session is SRTP. The RTCP SSRC fix is applied before the send. The plugin has no logger: the core's callbacks have no log method, so drop messages are not written. Checked live: attaches over HTTP. Wave 147: the PLI to the legacy peer is now sent (it was queued and never flushed) and SRTCP protect failures are logged; incoming RTCP SSRC fix matches C. Wave 151: PLI, close, legacy RTCP, notify and the peer relays resolve through session_of, and the relay task ends with its session. Wave 164: the C log lines for request refusals, lifecycle, recordings and forwarder requests are written after the state lock is released (janus_nosip.c 755-2530). Open: VERB, HUGE and DBG lines, and the relay-task lines (3189-3483). Wave 169: the relay, request and media log lines are written, and the SRTP error texts are verbatim (janus_nosip.c 3183-3488). Wave 177 audit: media routing and destroy-time socket closing are pinned by tests; the stale rows are closed in the log. Wave 215: the created, initialized and destroyed lines and the SRTP profile and key lines are written, and the inbound context reads the first master_len bytes of the decoded SDES key, as libsrtp does. Open: the allocation ERR lines need a reason from MediaSeam, and the relay resolve ERR lines need IPv6-capable resolution. Wave 236: the allocation ERR lines (janus_nosip.c 2873, 2891) and the relay resolve ERR lines (3228, 3237) are logged; 2928 needs a std socket error text and stays open. Wave 248: nosip-requests scenario; stream_id random like C; result key order; SSRC retention across plugin offers.
Plugin: Duktape (Boa)
Plugin shell and gateway host API on Boa, with the host checks wired to C's type-error text and session-missing errors. require resolution, module cache and CommonJS wrapper. Missing: circular-require registration, evaluation, the global require and file reads. Registered in the binary from janus.plugin.duktape.jcfg general.script, behind the plugin-duktape feature; checked live with a nine-hook script. Wave 152: the gateway host object is installed (pushEvent, closePc, relayRtp, the media and recipient setters, data relay), with the hangup reset and nonzero u32 session ids. Open: pushEvent with an SDP needs a janus-sdp dependency. Wave 153: pushEvent validates the SDP with janus-sdp and passes the JSEP to the core, with C's parse error message and the first video codec recorded. Wave 156: startRecording, stopRecording and the relayData warning are wired, and the script engine has a loop-iteration limit (janus_duktape.c 1300-1481). Open: readFile raw bytes, the scheduler, and the video-start recorder reset. Wave 161: the scheduler thread runs pokeScheduler and timeCallback, starts before init and stops before destroy (janus_duktape.c 493-531, 1710-1796, 2910-2960). Wave 165: thrown script errors carry C's duk_safe_to_string text. Open: readFile's invalid-UTF-8 mapping (needs a capture), the video-start recorder reset (needs a janus-rtp dependency), and the loop-limit policy (owner). Wave 174: the JSON error text of pushEvent and notifyEvent and the module name fields follow janus_duktape.c 568-679 and 285-393. Open: readFile on non-UTF-8 content (capture), the loop-limit policy and the session-id wrap (owner), the video-start recorder reset. Wave 177 audit: the Duktape rows are pinned by tests; the owner decisions stay open. Wave 199: the incomingRtp, incomingRtcp and incoming data hooks are called when the script defines them, with C's guards and the data recording before the hook (janus_duktape.c 2399-2601). The script-call API already existed, so the janus-script-js addition is byte_string_literal only. Open: the default media path with no hook (recording, recipient relay, REMB and PLI), invalid UTF-8 text (needs a capture), the deprecation log lines, and the loop-limit policy (owner). Core dispatch of incoming_rtp and incoming_rtcp is still open. Wave 211: with no hook, incomingRtp records the packet and asks for a keyframe on the setPliFreq interval; incomingRtcp caps a peer REMB and relays a PLI to the publisher through a sender back-link; data is relayed to ready recipients; hangup and destroy clear the recipient links (janus_duktape.c 2399-2625, 2741-2751, 2145-2152). Open: the RTP relay to recipients needs janus-rtp's header update (rtp.c 854), which the crate cannot reach without a Cargo change; the simulcast record branch follows the handleMessage RID setup. Wave 222: the handleMessage RID and SSRC setup (janus_duktape.c 2233-2244) makes the simulcast record branch reachable (2440, 2468). Open: the selector (2472-2473) and RID learning from the header extension (2448-2466) need a janus-rtp dependency (Cargo). Wave 226: the simulcast record runs the janus-rtp selector and RID learning, the saved header rewrite, and the video-start reset (janus_duktape.c 2448-2486, 1373-1376). Open: the RTP relay to recipients (2472-2503). Wave 227: the default path relays a copy to each started recipient through its own SwitchingContext (janus_duktape.c 2778-2786, 2867-2876), and the simulcast branch has a per-recipient selector, VP8 descriptor rewrite and the PLI and substream hooks (2787-2851). Open: pinning tests for the AV1 descriptor, the codec-NONE path, and the temporal hook. Wave 229: pinned the AV1 keyframe rule (the RTP payload keyframe, not the descriptor, rtp.c 1277-1298), the empty descriptor, the codec-NONE relay and the temporal hook gating (janus_duktape.c 2825-2834). Open: the undefined-hook error log. Wave 230: the temporal hook is gated on the substream hook alone (janus_duktape.c 2827), and a failed call logs Duktape error once per change (2834). Open: the substream and incoming-media hook error logs. Wave 232: a failed substream, incoming RTP, incoming RTCP or data hook call logs the engine error once per call (janus_duktape.c 2418-2426, 2540-2543, 2600-2603, 2818). Open: the setup, session, hangup, data-ready, slow-link and timer hooks still drop their errors. Wave 233: every Duktape hook the port calls logs a failed engine call once, with C's text (janus_duktape.c 1803, 2098, 2134, 2391, 2652, 2688, 2761, 2926, 2968); the scheduler waits for each engine call, as C holds duktape_mutex across its pcall. Open: the other load errors are logged but not individually pinned. Wave 234: the load errors (missing required hook, syntax error, empty script) are pinned: each is logged once at Err with C's text and the plugin does not start (janus_duktape.c 275, 1606, 1629, 1641). The syntax-error text is the engine's. Wave 236: the deprecated incomingData load warning, the missing incomingTextData warning, and the circular-require and retry-after-failed-load pins are ported with 6 tests. Wave 248: script-duktape-echotest and script-duktape-hostapi scenarios; hooks variant matches the oracle.
Plugin: Lua
Plugin lifecycle, config keys, host function table, argument checks and return shapes for all host functions, behind a LuaEngine seam. The engine is Lua 5.3 through mlua with vendored sources (ADR-0008, under ADR-0007's owner approval, exception entry in xtask purity). Scripts load and run, the nine required and three optional hooks are called with C's arguments, and the host API is registered as Lua globals and routed through LuaHost. The gateway bodies behind LuaHost are not wired yet (UnwiredHost answers testExtraFunction only). Still missing: timers and the scheduler, media relay (incomingRtp, incomingRtcp, data callbacks), JSEP handling in handleMessage, and the get* overrides. The purity build-dependency check is not enforced (see ADR-0008). Gateway host API, timers, media fan-out and JSEP answer handling are wired. Wave 146: binary payloads cross the engine seam as bytes both ways (ADR-0008 amended), and startRecording, stopRecording, frame saves and destroy-time close run through the core recorder hook (janus_core::recording). Open: simulcast layer selection for video saves, get* overrides. Wave 151: simulcast context (RID setup, SSRC learning, relay gating with the VP8 rewrite, substreamChanged and temporalLayerChanged, setSubstream and setTemporalLayer). Open: the recording layer choice (rec_simctx, janus_lua.c 2126-2139), get* overrides. Wave 152: the video recording follows the simulcast layer choice (rec_simctx and rec_ctx, janus_lua.c 2126-2139). Open: the get* overrides (janus_lua.c 1606-1760). Wave 153: the plugin info getters route to the script overrides (janus_lua.c 1606-1760), with the first text answer kept. Open: a non-text answer is asked again, not cached, because the trait cannot carry NULL; the number-to-text formatting differs from Lua for some floats. Wave 165: number to text follows the Lua 5.3 rules (%.14g with the integer-looking suffix), and string to number reads hexadecimal and refuses inf and nan (lobject.c luaO_str2d). Open: the non-text getter answer and the relayTextData non-UTF-8 payload, both owner decisions. Wave 177 audit: no portable open row remains; the non-text getter and the relayTextData payload are owner decisions. Wave 182: the plugin info getters return None for a NULL answer, and a text answer is cached (janus_lua.c 1646-1674). Open: the number-to-text edge cases that the wave reports list as known differences. Wave 242: relayTextData and the deprecated relayData relay a non-UTF-8 payload as raw bytes (janus_lua.c 1032-1083). Wave 244: the Lua plugin is registered in the janus binary behind the plugin-lua feature, read from janus.plugin.lua.jcfg or .cfg. Wave 248: script-lua-echotest, script-lua-hostapi and script-lua-videoroom scenarios, parity-media --script and --hooks against the oracle. Fixed: RTP header extensions dropped on the plain relay, relayRtp and relayRtcp going nowhere, data_ready never called, handleMessage JSON form. Wave 249: parity/lua-probe: every number pushed into Lua is a float, inf/nan text, luaO_str2num rules, saturating float casts, plugin keyed by get_package() after init; numbers, log lines and info text match the oracle.
Transports
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Transport: HTTP | 2351 | 92% | Open: the trait shape (owner), the binary's AuthCheck (janus/src), and jansson formatting bytes (no source in the oracle). Open: admin destroy and janus.c 1342 send no session_over. Open: TLS, lws frame leniency. |
| Transport: WebSockets | 1577 | 94% | Open: the http-only upgrade case needs a Docker capture; subprotocol and ping semantics need libwebsockets; the reclaim window is core-owned. Open: pfunix_listener still destroys sessions at close (janus/src follow-up). Open: TLS, Sec-WebSocket-Version 8 and 12. |
| Transport: Unix socket | 943 | 97% | Open: events the core raises outside a request do not yet reach pfunix clients. Open: the bind and listen lines, which the binary performs. Open: a timed-out stream client keeps a stale instance entry, which needs a hook in transports/pfunix. |
| Transport: MQTT | 1757 | 95% | Open: rumqttc hides the bad filter on 3.1.1, the refused-SUBACK log line on 3.1, the paho return codes, and live broker capture. Open: the binary's configured_transport returns None before init (janus/src); the rumqttc limits; the Paho-only lines; the live-broker rows. Open: the 3.1.1 and MQTT 5 invalid-filter send is blocked by rumqttc. |
| Transport: RabbitMQ | 1121 | 92% | Missing: AMQP I/O, the reconnect loop, the method-frame skip. |
| Transport: nanomsg | 619 | 88% | Open: the janus/src and Cargo wiring; one peer at a time and the 1 MiB receive limit are deliberate choices. Open: C emits created before its thread starts (documented). Open: ws://, PUB back-pressure, the admin API comparison. |
Full notes for this group
Transport: HTTP
Routing, CORS, long-poll, response shapes, ACL and admin ACL config, connection events. Missing: MHD daemon, sockets, threads. A tokio and hyper listener (server.rs) serves the existing routes through a dispatch trait. Core dispatch is not wired; a long poll would block a worker thread, so dispatch needs spawn_blocking or async. The binary enables the listener from janus.transport.http.jcfg (or .cfg) general.http and general.port, and was checked live: OPTIONS on the configured port returns 200 with CORS headers, and with no config nothing is bound. general.acl is enforced on connect, as janus_http.c does, and the admin HTTP listener (admin_http, admin_port) is served and checked live with a ping. Interface name resolution and TLS are still refused. general.interface and admin_interface resolve by name through if-addrs (first IPv4 of a running interface, as janus_http.c does). Checked live: lo0 serves a 200, an unknown name is refused. Wave 172: the HTTP route follows the transport config (see the wave's log rows). Waves 176-178: OPTIONS outside and under the base path have live-socket tests (janus_http.c 1461-1481). Open: the MHD behaviour after a second reply, the jansson bytes (janus-json), the apisecret trait shape (owner), and the connections counters. HTTP long-poll token check now uses the core's token check (janus_http.c 1606-1622; auth.c 208-221), so valid tokens pass. Open: the MHD behaviour after a second reply, the jansson bytes, the apisecret trait shape (owner). Wave 190: the connections object is filled from per-daemon open counts for the plain Janus API and admin listeners (janus_http.c 1292-1320). Open: the binary has no admin route to query_transport yet; https and admin_https are not counted (TLS). Wave 212: a missing apisecret or token is no longer passed to the auth trait as an empty string, since C passes NULL (janus.c 3564-3577, janus_http.c 1603-1641). Open: the trait shape (owner), the binary's AuthCheck (janus/src), and jansson formatting bytes (no source in the oracle). Wave 229: the binary drains the reclaim notices to the HTTP long-poll queues: session_over removes the queue and session_claimed replaces it and answers the waiting polls (janus_http.c 1177-1230). WebSocket, shared and pfunix notices have no C consumer and are dropped. Open: admin destroy and janus.c 1342 send no session_over. Wave 232: a successful client destroy no longer clears every HTTP queue; only the owner's session_over, applied by the drain, removes a queue (janus.c destroy, janus_http.c 1177-1184). Wave 247: jansson 2.14 port in janus-json; MHD refusal and chunking rules; 5 new oracle scenarios. Open: TLS, lws frame leniency. Wave 248: HTTPS and admin HTTPS through janus-tls (rustls 0.23 on graviola, ADR-0009), checked against the oracle: replies, start-up lines and failure cases (missing key, wrong password, bad cipher string).
Transport: WebSockets
Subprotocols, frames, ping and pong, ACL, configure partial apply, log-level mask, origin rule. Missing: sockets, TLS, lws callbacks, service thread. A tokio and tungstenite listener (server.rs) does the handshake, the ACL and the frame loop, with a loopback test. The binary starts it from janus.transport.websockets.jcfg, checked live with a 101 upgrade. A denied peer gets the 101 and then the close, as C does. Requests are answered by the same core as HTTP, checked live with a ping and a pong. Async plugin events and session timeouts are not delivered to WebSocket sessions yet, and closing a socket does not end its sessions. Async events and session timeouts reach the owning connection, and closing a socket ends its sessions, as janus_websockets.c does. Reclaim window not honoured. Wave 172: the libwebsockets log bits follow lws-logs.h (LLL_COUNT is 12), so the count setting turns on NOTICE and INFO (janus_websockets.c 610-646, 1059-1095). Wave 213: the upgrade response carries the Access-Control headers C adds (janus_websockets.c 1258-1315), and a plain HTTP request on the endpoint gets a 403 (1150-1161). Open: the http-only upgrade case needs a Docker capture; subprotocol and ping semantics need libwebsockets; the reclaim window is core-owned. Wave 226: a closed WebSocket keeps its sessions for the reclaim window (janus.c 3525-3558); each connection has its own core instance, and a claim binds the session's events to the claiming connection. Open: pfunix_listener still destroys sessions at close (janus/src follow-up). Wave 247: ws-edge scenario, jansson multi-message loop. Open: TLS, Sec-WebSocket-Version 8 and 12. Wave 248: wss and admin wss, same checks against the oracle.
Transport: Unix socket
Framing, routing, admin rules, systemd fd mapping, shutdown unlink. Datagram and SEQPACKET socket I/O on tokio and libc (socket.rs, seqpacket.rs). The binary starts it from janus.transport.pfunix.jcfg (or .cfg) when general.enabled or admin.admin_enabled is set. Checked live: DGRAM on macOS and SEQPACKET (the default) on Linux, each with a real client getting server_info replies with matching transactions; a SEQPACKET client that disconnects leaves janus running. The DGRAM receive reads the sender path as C's strlen does, which fixed a one-byte loss that std's recv_from caused. Missing: events (none are sent to Unix socket clients), the C log lines (routed through janus_log_level), the secret and token checks, and a worker pool for message requests. macOS refuses SEQPACKET, which C's default also does, so a default config there does not start the transport. Wave 172: sessions are bound to the transport that created them, and events go to the client by session id, as the WebSocket transport does. Open: events the core raises outside a request do not yet reach pfunix clients. Waves 176-178: the written reply line is logged at HUGE (janus_pfunix.c 725), and out-of-request events reach pfunix clients. Open: the bind and listen lines, which the binary performs. Wave 180: the bind, listen, truncation and freeing log lines (janus_pfunix.c 165, 184-210), and the binary binds Janus then Admin as C does. Wave 228: a closed stream client keeps its sessions for reclaim_session_timeout and a claim inside the window moves them (janus.c 3525-3558, 1392); each connection has its own core instance. Open: a timed-out stream client keeps a stale instance entry, which needs a hook in transports/pfunix. Wave 230: a timed-out stream client reports through a timeout hook, and the binary drops its instance entry, so a reused address starts fresh (janus_pfunix.c session timeout). Normal closes are unchanged.
Transport: MQTT
Topics, routing, reply rules, connect options, last-will and disconnect timeout. Missing: Paho I/O and the vacuum thread. Socket I/O on rumqttc with reconnect (1s doubling to 60s). MQTT 5 and 3.1 run; TLS and ws refused. The binary starts it from janus.transport.mqtt.jcfg when general.enabled is set, with the URL and topics from the config. Checked live: it starts and keeps retrying against a closed broker without crashing. Checked live against mosquitto 2.1.2 on the default MQTT 3.1.1: a request on the subscribe topic got a server_info reply on the publish topic. MQTT 3.1 is not yet checked against a live broker. MQTT 5 runs on rumqttc's v5 client with user properties. MQTT 3.1 runs on a hand-written codec and client (MQIsdp, level 3), tested against a fake broker; QoS 2 is refused for 3.1. Live check on mosquitto 2.1.2 with mqtt_version 5 (Wave 135): a request on janus/in got a server_info reply on janus/out, and the transport connected event reached the MQTT event handler. A session takeover makes the broker disconnect janus, and janus reports reconnecting and then reconnects; the disconnected event was not seen live, since the broker closes the socket straight after its DISCONNECT. Wave 167: MQTT 3.1 runs the QoS 2 exchange on publish and receive (janus_mqtt.c 1074-1107, 1515, 1539, 1631), verified on a fake broker only. The event handler still refuses QoS 2 on 3.1 for its own publishes. Open: a live 3.1 QoS 2 run against mosquitto. Wave 172: sessions are bound to their MQTT transport, and events go to the client by session id. Open: the same out-of-request event fallback as pfunix. Waves 176-178: the MQTT transport logs its runtime and config lines through the core log (janus_mqtt.c). A live 3.1 QoS 1 and QoS 2 round trip passes against mosquitto 2.1.2 (ignored tests, JANUS_MQTT_TEST_BROKER). Open: the reconnect QoS 2 resend live, and the MQTT log lines listed in the wave 178 report. Wave 180: the NULL-topic publish ERR, and the vacuum thread start and finish VERB lines (janus_mqtt.c 908, 1725, 1727). Open: the Paho callback and return-code lines, which rumqttc does not expose; the both-APIs-disabled WARN (656); the subscribe-refusal logic divergence. Wave 197: MQTT 3.1 CONNECT sets the user name and password flags, and writes each field, only when present (janus_mqttevh.c 376-377). The transport keeps its guest defaults, so its pinned bytes are unchanged. On 3.1 an invalid filter is sent to the broker, and a QoS Paho would refuse is logged, not sent (janus_mqtt.c 1083, 1089, 1441). Open: rumqttc hides the bad filter on 3.1.1, the refused-SUBACK log line on 3.1, the paho return codes, and live broker capture. Wave 215: with both APIs disabled, init writes the WARN after the config and events lines, gives up, and clears its state (janus_mqtt.c 655-658). Open: the binary's configured_transport returns None before init (janus/src); the rumqttc limits; the Paho-only lines; the live-broker rows. Wave 241: the three live broker tests pass against Mosquitto 2.1.2; invalid filters close the link on the broker (no SUBACK 0x80), captured. Open: the 3.1.1 and MQTT 5 invalid-filter send is blocked by rumqttc. Wave 248: MQTT TLS (ssl_enabled, cacertfile, certfile, keyfile, verify_peer) matches C in 9 cases; ssl:// with TLS off is refused with -6 as C does.
Transport: RabbitMQ
Routing, properties, init config keys, reconnect backoff, declare plan, AMQP frame parsing and body accumulation, content-header property decoding, basic.deliver decode. Missing: AMQP I/O, the reconnect loop, the method-frame skip. Socket I/O on lapin 2.5 with the declare plan and in-thread reconnect. TLS and no-API refused. The binary starts it from janus.transport.rabbitmq.jcfg when general.enabled is set. Checked live against RabbitMQ 4.4.0: a request on to_janus got a server_info reply on from_janus, with queue_durable = true and declare_outgoing_queue = no. RabbitMQ 4 refuses the transient queues that C's defaults declare, so a default config cannot start here (see PARITY_LOG Wave 102). Wave 171: the inbound RabbitMQ sequence follows janus_rabbitmq.c 997-1044. Wave 175: RabbitMQ sessions are routed per session id, following the pfunix and MQTT design (wave 172). BrokerCore binds on create and unbinds on destroy. Wave 248: RabbitMQ TLS matches C in 7 cases.
Transport: nanomsg
Framing, 8191-byte limit, socket plan, poll write interest. Socket I/O missing. Wave 212: a single-peer NN_PAIR socket does the bind or connect over tcp and ipc, the SP handshake, 8-byte big-endian framing and one service thread (janus_nanomsg.c 193-284, 507-619). Not checked against libnanomsg. Open: the janus/src and Cargo wiring; one peer at a time and the 1 MiB receive limit are deliberate choices. Wave 238: a failed bind disables its API and drops its queued messages; the created event fires after a successful bind (janus_nanomsg.c 221-227, 302-323, 390-396). Open: C emits created before its thread starts (documented). Wave 244: the transport is started by the janus binary from janus.transport.nanomsg.jcfg or .cfg, with a session route for events and an end-to-end exchange over TCP. Wire compatibility with libnanomsg is captured but a live comparison is open. Wave 249: compared live with libnanomsg 1.1.5. Fixed ipc framing (type byte), stale ipc file bind, reserved header bytes, header timeout and second peer, address rules (80 rows), and the created event order and socket numbers. The janus binary's replies equal the C gateway's byte for byte. Open: ws://, PUB back-pressure, the admin API comparison.
Event handlers
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Event handler: GELF | 616 | 96% | Missing: TCP (refused, not ported), the transport events (not sent to handlers yet), the transport name in the created body, and the admin request. |
| Event handler: MQTT | 1274 | 97% | Missing: MQTT 5 user properties from the config file (the binary passes none), TLS and ws (refused), and the Paho thread model (tokio replaces it). Open: the password-without-user-name case (Paho source not in the repo), and the bad-QoS return code, which rumqttc does not expose. |
| Event handler: nanomsg | 521 | 90% | Open: the binary does not start the handler (janus/src and Cargo). Open: a SUB peer that sends data (libnanomsg aborts, the port discards). |
| Event handler: RabbitMQ | 718 | 95% | Missing: TLS (refused, no TLS build), and the Admin API tweak through the binary (the core calls it, the connection is not restarted). |
| Event handler: sample HTTP | 796 | 96% | Missing: https (refused, no TLS), plugin and transport events (not sent to handlers yet), and the C thread model (a tokio future replaces it). |
| Event handler: WebSocket | 942 | 96% | Missing: wss:// (refused, no TLS), the libwebsockets logging hooks (ws_logging is read but not emitted), and the C thread model (the client is a std thread). |
Full notes for this group
Event handler: GELF
Message building and UDP chunking, and the EventHandler: config keys, UDP send from a sender thread, and the session events. Wave 106: the core raises the session created and destroyed events, and the plugin events from notify_event, and sends them to the handlers (Wave 109), and the binary loads janus.eventhandler.gelfevh.jcfg. Checked live on macOS: a create and a destroy over the Unix socket reached a UDP listener as two GELF messages. Missing: TCP (refused, not ported), the transport events (not sent to handlers yet), the transport name in the created body, and the admin request. Compression uses flate2's miniz backend, so the gzip bytes differ from zlib. Wave 168: TCP transport and the admin tweak request (janus_gelfevh.c 112-120, 201-211, 349-355, 470-560). Wave 173: destroy joins the sender, so queued events go out first (janus_gelfevh.c 404-412). Wave 247: handler gaps closed except TLS.
Event handler: MQTT
Payload, grouping, topics, connection and TLS keys, MQTT 5 user properties, refusal texts, and the publish path on rumqttc (3.1.1 and 5) with reconnect, MQTT 3.1 through the transport's codec. Wave 107: the binary loads janus.eventhandler.mqttevh.jcfg, registers the handler with the core, and runs its connection. Checked live against mosquitto 2.1.2: the connected status, then the created and destroyed session events, on janus/events/sessions. Missing: MQTT 5 user properties from the config file (the binary passes none), TLS and ws (refused), and the Paho thread model (tokio replaces it). Wave 171: the event handler's acknowledgement handling for QoS 2 packets. Wave 197: the 3.1 evh CONNECT sends the credential flags only when the settings have them, as C does (janus_mqttevh.c 376-377, 766-778). This changes the default evh CONNECT. Open: the password-without-user-name case (Paho source not in the repo), and the bad-QoS return code, which rumqttc does not expose. Wave 247: handler gaps closed except TLS. Wave 248: event handler TLS keys reach the client (tls_cacert, tls_client_cert, tls_client_key, tls_verify_peer).
Event handler: nanomsg
Payload and framing. Socket I/O missing. Wave 171: the nanomsg PUB publisher over tcp:// and ipc:// with the SP handshake (janus_nanomsgevh.c 193-218, 462-516). Wave 214: an unknown address scheme fails at init, as nn_connect and nn_bind refuse it (janus_nanomsgevh.c 198-213). Open: the binary does not start the handler (janus/src and Cargo). Wave 244: the event handler is started by the janus binary from janus.eventhandler.nanomsgevh.jcfg or .cfg, with the run_publisher thread and a real subscriber test. Wave 247: handler gaps closed. Wave 249: compared live with libnanomsg: PUB/SUB wire, ipc framing, address rules, reconnect timing. Open: a SUB peer that sends data (libnanomsg aborts, the port discards).
Event handler: RabbitMQ
Payload, routing, heartbeat backoff, login and SSL rules, config file names, and the AMQP publish path on lapin (connect, declare the exchange and the outgoing queue, publish each batch, retry after retry_interval). Wave 112: the binary loads janus.eventhandler.rabbitmqevh.jcfg (or .cfg) and registers the handler with the core. Checked live against RabbitMQ 4.4.0: the created and destroyed session events arrived on a durable queue bound to the handler's exchange and routing key. The heartbeat check (jns_rmqevh_hrtbt) runs when heartbeat is above 0: a lost link is rebuilt without a publish. Checked live against RabbitMQ 4.4.0 (Wave 128): with heartbeat 2 the link came back after a broker restart, and with heartbeat 0 it did not, as in C. Missing: TLS (refused, no TLS build), and the Admin API tweak through the binary (the core calls it, the connection is not restarted). On RabbitMQ 4 the outgoing queue needs declare_outgoing_queue = "no". Wave 247: handler gaps closed except TLS. Wave 248: event handler TLS; init failure retries every 5 s where C aborts the init connect.
Event handler: sample HTTP
Payload, grouping, retry, auth, init keys and defaults, tweak request, incoming gate, body encoding, and the HTTP client on hyper with C's retry count and doubling backoff. Wave 108: SampleHandler is the EventHandler, with a queue and a worker future that the binary runs in the runtime; tweak changes are seen by the next batch. Checked live against a local HTTP server: the created and destroyed session events arrived as POST bodies, grouped as C's default does. Missing: https (refused, no TLS), plugin and transport events (not sent to handlers yet), and the C thread model (a tokio future replaces it). Wave 247: handler gaps closed except https. Wave 248: https backend through rustls; untrusted certificate fails as in C.
Event handler: WebSocket
Payload, grouping, config keys, backend path, reconnect delay, partial writes, and the tungstenite client on ws:// with C's reconnect delays. Wave 111: the binary loads janus.eventhandler.wsevh.jcfg (or .cfg), registers the handler with the core, and keeps its client thread running. Checked live against a local WebSocket server that echoes the subprotocol: the created and destroyed session events arrived as two text frames. Missing: wss:// (refused, no TLS), the libwebsockets logging hooks (ws_logging is read but not emitted), and the C thread model (the client is a std thread). Wave 176: the libwebsockets log-line format (janus_wsevh.c 151-154). Wave 247: handler gaps closed except wss. Wave 248: wss backend; untrusted certificate fails as in C.
Loggers
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Logger: JSON file | 356 | 92% | – |
Full notes for this group
Logger: JSON file
Logger trait and file writer. jansson byte formatting unverified. Wave 171: jsonlog logger changes (see the wave's log rows). Wave 247: output checked against libjansson 2.14 bytes.
Tools
| Component | C lines | Ported | What is still open |
|---|---|---|---|
| Tool: janus-cfgconv | 151 | 97% | Wave 247: checked against the C converter over 3626 cases; one flex stray-backslash echo on an unterminated @include is not ported. |
| Tool: pcap2mjr | 386 | 95% | – |
| Tool: mjr2pcap | 399 | 95% | – |
| Tool: janus-pp-rec | 1912 | 99% | Open: Opus, MP4, MKV and metadata output (libavformat), and the truncated-frame capture. Open: a file with no real header after the empty one still exits 1. Open: some video info-pass log lines, H.264/H.265 to .mkv. |
| Tool: pp-rec codecs and muxers | 3789 | 95% | Not wired end to end. Open: pp-vp8.c and pp-mp4.c are absent from the oracle; AV1 uvlc and max-size truncation need owner decisions; the H.264 crop and MP4 playability need captures; the G.722 decoder has no C source; silence fill is built in memory per frame. |
Full notes for this group
Tool: janus-cfgconv
Both directions and messages. Wave 247: checked against the C converter over 3626 cases; one flex stray-backslash echo on an unterminated @include is not ported.
Tool: pcap2mjr
Classic pcap reader. pcapng missing. Wave 247: checked against the C tool over 5075 cases (pcapng, snaplen/caplen grid, argument fuzz).
Tool: mjr2pcap
Converter and pcap writer. Wave 247: checked against the C tool built in the oracle image over 1692 cases.
Tool: janus-pp-rec
Inspection modes, WAV, binary, SRT. Header parsing for VP8, H.264, H.265 and AV1, running maximum size, -e report with jansson-style floats, VP8 depayload, WebM over encoded frames, WebM preprocess and payload resolution, G.722 gap fill. Packet reorder with duplicate drop and the timestamp rewrite (--ignore-rtp-ts) with the audio clock check are wired into the -p walk and info pass. Latency, restamp and audio skew are pure functions, not yet wired. Padding and silence-suppression drops not ported. Decode still refused; VP9 missing. Restamp, audio skew and latency (G.711 and L16 convert) are wired into the convert pass; restamp options other than the default window are not checked against a live C run. Wave 167: the -p walk keeps C's ordered list and 64-bit keys (pp-rec.c 1169-1214), with padding, silence and the header-only rule, and the video preprocess (96-121, 271-320). Open: the convert path. Wave 170: silence suppression in the audio convert path (janus-pp-rec.c 978-1005, 787-788), and VP8 and VP9 to webm end to end. Wave 175: text2pcap folder creation follows utils.c 312-337 with a step planner and an I/O wrapper in janus-record. Wave 176: pcap2mjr and mjr2pcap tool rows checked (pcap2mjr.c 161, 294-308). Wave 214: the walker's late-packet and timestamp-reset WARNs, the CSRC and extension VERB lines, the too-large extension stop, the -p timing lines, and the convert path's clock check and ignore-rtp-ts rewrite (janus-pp-rec.c 901-1204). Open: Opus, MP4, MKV and metadata output (libavformat), and the truncated-frame capture. Wave 221: the Opus extradata, RED split, gap check and silence positions are ported, with 22 tests. pp-opus.c has no decoder; the output step is an Ogg and Matroska muxer, which is open. Wave 242: leading zero-length MJ info headers are skipped and file offsets in warnings count the skipped bytes (janus-pp-rec.c 483). Open: a file with no real header after the empty one still exits 1. Wave 247: CLI front end is a literal port of janus-pp-rec.c main(); jansson 2.14 loads/dumps; 397 differential scenarios and ~5000 random recordings match the C tool. Open: some video info-pass log lines, H.264/H.265 to .mkv. Wave 248: video info pass is a literal port of janus_pp_*_preprocess (0 of 528 differing cases); H.264 and H.265 to .mkv are byte-exact with the C tool.
Tool: pp-rec codecs and muxers
G.711 and L16 WAV and SRT. G.722 decoder and process pass over frames; WebM EBML writer with timestamp and cluster rules. MP4 box writer (ftyp, moov, faststart mdat, sample tables) with co64 and 64-bit mdat past 2^32 bytes, round-trip tested; unwired, and sample entries for avc1, hvc1 and av01 are caller-supplied, so H.264 and H.265 MP4 output is not yet playable. The MP4 box rules are ISO-BMFF, not checked against ffmpeg (C's pp-avformat.c has no box code). H.264, H.265 and AV1 dimension parsing. Not wired end to end. Opus, VP8/9 decode missing; Opus candidate fails G3 at 1-LSB differences. Wave 212: the G.722 gap WARN tests the raw timestamp step (pp-g722.c 128-130), and the C int truncation and backward-timestamp wrap are pinned. Most open G.722 rows were stale. Open: pp-vp8.c and pp-mp4.c are absent from the oracle; AV1 uvlc and max-size truncation need owner decisions; the H.264 crop and MP4 playability need captures; the G.722 decoder has no C source; silence fill is built in memory per frame. Wave 242: the AV1 sequence header follows C's quirks: uvlc not skipped, the operating-parameters skip is 2*bdlm1+1 bits, and sizes stored as uint16 (pp-av1.c 148, 184, 202-204), pinned against the C parser compiled and run locally. Wave 247: G.711, G.722, L16, srt and binary outputs match the C tool. Wave 248: video info-pass logs and mkv output of H.264/H.265 match C.
Replay scenarios
Each scenario is a recorded C-server exchange (parity/traces/<id>/normalised.json) that cargo run -p xtask -- parity-run replays against the Rust binary. All 90 below are marked verified.
| Scenario | What it covers | State |
|---|---|---|
info | Admin and client info reply | verified |
session | create, attach, echotest configure | verified |
echotest | echotest create, configure with jsep, trickle, hangup, destroy | verified |
audiobridge | audiobridge create, list, exists, destroy | verified |
admin | Admin API: status, sessions, handles, handle_info, log level, query_eventhandler | verified |
videoroom | videoroom create, exists, list, edit, allowed, kick, destroy | verified |
streaming | streaming rtp mountpoint create, list, info, destroy | verified |
textroom | textroom create, list, destroy | verified |
videocall | videocall register, list, call between two handles | verified |
recordplay-list | recordplay list | verified |
http-longpoll | http long poll with maxev, event and keepalive | verified |
ws-session | websocket create, attach, keepalive | verified |
sdp-admin-audioonly | Real-browser SDP offer/answer exchange: admin audioonly | verified |
sdp-admin-chrome | Real-browser SDP offer/answer exchange: admin chrome | verified |
sdp-admin-datachannel | Real-browser SDP offer/answer exchange: admin datachannel | verified |
sdp-admin-firefox | Real-browser SDP offer/answer exchange: admin firefox | verified |
sdp-admin-safari | Real-browser SDP offer/answer exchange: admin safari | verified |
sdp-audiobridge-audioonly | Real-browser SDP offer/answer exchange: audiobridge audioonly | verified |
sdp-audiobridge-chrome | Real-browser SDP offer/answer exchange: audiobridge chrome | verified |
sdp-audiobridge-datachannel | Real-browser SDP offer/answer exchange: audiobridge datachannel | verified |
sdp-audiobridge-firefox | Real-browser SDP offer/answer exchange: audiobridge firefox | verified |
sdp-audiobridge-pcmu | Real-browser SDP offer/answer exchange: audiobridge pcmu | verified |
sdp-audiobridge-safari | Real-browser SDP offer/answer exchange: audiobridge safari | verified |
sdp-audioonly-echotest | Real-browser SDP offer/answer exchange: audioonly echotest | verified |
sdp-chrome-echotest | Real-browser SDP offer/answer exchange: chrome echotest | verified |
sdp-datachannel-echotest | Real-browser SDP offer/answer exchange: datachannel echotest | verified |
sdp-firefox-echotest | Real-browser SDP offer/answer exchange: firefox echotest | verified |
sdp-firefox-recvonly-echotest | Real-browser SDP offer/answer exchange: firefox recvonly echotest | verified |
sdp-invalid-echotest | Real-browser SDP offer/answer exchange: invalid echotest | verified |
sdp-nosip-audioonly | Real-browser SDP offer/answer exchange: nosip audioonly | verified |
sdp-nosip-chrome | Real-browser SDP offer/answer exchange: nosip chrome | verified |
sdp-nosip-datachannel | Real-browser SDP offer/answer exchange: nosip datachannel | verified |
sdp-nosip-firefox | Real-browser SDP offer/answer exchange: nosip firefox | verified |
sdp-nosip-safari | Real-browser SDP offer/answer exchange: nosip safari | verified |
sdp-recordplay-audioonly | Real-browser SDP offer/answer exchange: recordplay audioonly | verified |
sdp-recordplay-chrome | Real-browser SDP offer/answer exchange: recordplay chrome | verified |
sdp-recordplay-datachannel | Real-browser SDP offer/answer exchange: recordplay datachannel | verified |
sdp-recordplay-firefox | Real-browser SDP offer/answer exchange: recordplay firefox | verified |
sdp-recordplay-play | Real-browser SDP offer/answer exchange: recordplay play | verified |
sdp-recordplay-safari | Real-browser SDP offer/answer exchange: recordplay safari | verified |
sdp-reneg-echotest | Real-browser SDP offer/answer exchange: reneg echotest | verified |
sdp-safari-echotest | Real-browser SDP offer/answer exchange: safari echotest | verified |
sdp-streaming-watch | Real-browser SDP offer/answer exchange: streaming watch | verified |
sdp-textroom-setup | Real-browser SDP offer/answer exchange: textroom setup | verified |
sdp-trickle-echotest | Real-browser SDP offer/answer exchange: trickle echotest | verified |
sdp-videocall-audioonly | Real-browser SDP offer/answer exchange: videocall audioonly | verified |
sdp-videocall-chrome | Real-browser SDP offer/answer exchange: videocall chrome | verified |
sdp-videocall-datachannel | Real-browser SDP offer/answer exchange: videocall datachannel | verified |
sdp-videocall-firefox | Real-browser SDP offer/answer exchange: videocall firefox | verified |
sdp-videocall-reneg | Real-browser SDP offer/answer exchange: videocall reneg | verified |
sdp-videocall-safari | Real-browser SDP offer/answer exchange: videocall safari | verified |
sdp-videoroom-audioonly-video-room | Real-browser SDP offer/answer exchange: videoroom audioonly video room | verified |
sdp-videoroom-audioonly | Real-browser SDP offer/answer exchange: videoroom audioonly | verified |
sdp-videoroom-chrome | Real-browser SDP offer/answer exchange: videoroom chrome | verified |
sdp-videoroom-datachannel-nodata | Real-browser SDP offer/answer exchange: videoroom datachannel nodata | verified |
sdp-videoroom-datachannel | Real-browser SDP offer/answer exchange: videoroom datachannel | verified |
sdp-videoroom-firefox-recvonly | Real-browser SDP offer/answer exchange: videoroom firefox recvonly | verified |
sdp-videoroom-firefox | Real-browser SDP offer/answer exchange: videoroom firefox | verified |
sdp-videoroom-reneg | Real-browser SDP offer/answer exchange: videoroom reneg | verified |
sdp-videoroom-safari-vp8-room | Real-browser SDP offer/answer exchange: videoroom safari vp8 room | verified |
sdp-videoroom-safari | Real-browser SDP offer/answer exchange: videoroom safari | verified |
sdp-videoroom-trickle | Real-browser SDP offer/answer exchange: videoroom trickle | verified |
media-loopback | Loopback WebRTC client: 11 media-plane scenarios plus 10 script-plugin and 8 script-hook variants against the C oracle and the Rust server (xtask parity-media [--script|--hooks]) | verified |
http-edge | Transport edge cases captured from the oracle: http edge | verified |
http-cors | Transport edge cases captured from the oracle: http cors | verified |
http-base-gw | Transport edge cases captured from the oracle: http base gw | verified |
http-base-root | Transport edge cases captured from the oracle: http base root | verified |
ws-edge | Transport edge cases captured from the oracle: ws edge | verified |
admin-settings | Oracle-captured scenario: admin settings | verified |
admin-trickle-state | Oracle-captured scenario: admin trickle state | verified |
http-nat-loops | Oracle-captured scenario: http nat loops | verified |
http-nat-settings | Oracle-captured scenario: http nat settings | verified |
nat-full-trickle | Oracle-captured scenario: nat full trickle | verified |
nat-ice-lite | Oracle-captured scenario: nat ice lite | verified |
nat-ice-tcp-lite | Oracle-captured scenario: nat ice tcp lite | verified |
nat-ice-tcp | Oracle-captured scenario: nat ice tcp | verified |
nosip-requests | Oracle-captured scenario: nosip requests | verified |
plugins-admin-api | Oracle-captured scenario: plugins admin api | verified |
recordplay-admin-key | Oracle-captured scenario: recordplay admin key | verified |
recordplay-requests | Oracle-captured scenario: recordplay requests | verified |
script-duktape-echotest | Oracle-captured scenario: script duktape echotest | verified |
script-duktape-hostapi | Oracle-captured scenario: script duktape hostapi | verified |
script-lua-echotest | Oracle-captured scenario: script lua echotest | verified |
script-lua-hostapi | Oracle-captured scenario: script lua hostapi | verified |
script-lua-videoroom | Oracle-captured scenario: script lua videoroom | verified |
sip-requests | Oracle-captured scenario: sip requests | verified |
textroom-admin-key | Oracle-captured scenario: textroom admin key | verified |
textroom-requests | Oracle-captured scenario: textroom requests | verified |
textroom-string-ids | Oracle-captured scenario: textroom string ids | verified |
videocall-requests | Oracle-captured scenario: videocall requests | verified |
Decisions and deviations log
PARITY_LOG.md is the project's running record of oracle defects the port keeps on purpose, deviations from C, and items not yet checked against the oracle. It has 263 sections:
- Review procedure
- Oracle defects and quirks found while porting
- Open verification items (unverified until checked against the oracle)
- Path rules (janus-record)
- Event-handler divergences (janus-evh-gelf)
- Open parity gap (janus-record reader)
- Core design decision: ack versus async event ordering (janus-core
Plugin), resolved - Tool deviations (pcap2mjr)
- Forwarder deviations and unverified details (janus-rtpfwd)
- Plugin wiring gaps (janus-core
Pluginand plugins) - Core API dispatch deviations (janus-core
api) - pp-rec inspection-mode deviations (janus-pp-rec)
- RecordPlay deviations (janus-plugin-recordplay)
- TextRoom deviations (janus-plugin-textroom)
- Sample event handler details (eventhandlers/sample)
- Streaming deviations and unverified details (janus-plugin-streaming)
- AudioBridge deviations and gaps (janus-plugin-audiobridge)
- VideoRoom gaps and security notes (janus-plugin-videoroom)
- ICE pair and check-list details (janus-ice
pairs) - SCTP association and HTTP transport gaps
- NoSIP gaps (janus-plugin-nosip)
- DTLS handshake risks (janus-dtls, dimpl 0.9.0)
- SIP plugin details (janus-plugin-sip)
- Identifier and RNG decisions
- Plugin identifier follow-ups
- Parity harness: first live oracle capture (info)
- WebSocket transport decisions (janus-transport-websockets)
- Streaming identifiers (resolved)
- Info reply baseline (resolved)
- Minimum supported Rust version
- Relay API gaps (resolved in part,
69e50c9) - Event handler decisions (janus-evh-ws, janus-evh-mqtt)
- Toolchain lint drift (rustc 1.98 / clippy)
- Videoroom rewrite (resolved in part)
- Wave 16 follow-ups
- Lua engine gate G5 (probe facts, decision pending with the user)
- ICE agent (janus-ice
agent) - RabbitMQ and nanomsg transport decisions
- pfunix and MQTT transport decisions
- JSON format consolidation
- SCTP stream reset (janus-sctp RE-CONFIG)
- ICE core wiring (janus-core
ice_glue) - Echotest session parity against the live C trace (
plugins/echotest/tests/parity_session.rs) - Wave 20 merges (code-only branches, verified on main)
- PeerMedia SRTP and sender SSRC (resolved in
ba93c35) - NoSIP media through a send seam (resolved in
d949811) - RecordPlay playback relays (resolved in
c90e36a) - Peer glue (resolved in
a124b8f) - Duktape remaining host API (resolved in
50d051d) - Decision: IceAgent ownership (resolved by coordinator)
- Wave 27 and 28 deviations and open items (code-only branches, not yet wired)
- Wave 29 deviations and open items
- Wave 30 and 31 deviations and open items
- Wave 32 deviations and open items
- Wave 33 deviations and open items
- Wave 34 deviations and open items
- Wave 35 deviations and open items
- Wave 36 deviations and open items
- Wave 37 deviations and open items
- Opus G3 check and Wave 38 deviations
- Opus G3 reference results (libopus 1.5.2, RFC 8251 vectors)
- Wave 39 deviations
- Wave 40 and the Opus mono audit
- Opus fork D (integrated patches A, B, C)
- Vendored Opus decoder (
crates/janus-opus) - Wave 41 deviations and open items
- Wave 42 deviations and open items
- Wave 43 deviations and open items
- Wave 44 deviations and open items
- Wave 45 deviations and open items
- Wave 46 deviations and open items
- Wave 47 deviations and open items
- Wave 48 deviations and open items
- Wave 49 deviations and open items
- Wave 50 deviations and open items
- Wave 51 deviations and open items
- Wave 52 deviations and open items
- Wave 53 and 54 deviations and open items
- Wave 55 deviations and open items
- Wave 56 deviations and open items
- Wave 57 deviations and open items
- Wave 58 deviations and open items
- Wave 59 deviations and open items
- Wave 60 deviations and open items
- Wave 61 deviations and open items
- Wave 62 deviations and open items
- Wave 63 deviations and open items
- Wave 65 deviations and open items
- Wave 66 deviations and open items
- Wave 67 deviations and open items
- Wave 68 deviations and open items
- Wave 69 deviations and open items
- Wave 70 deviations and open items
- Wave 71 deviations and open items
- Wave 72 deviations and open items
- Wave 73 deviations and open items
- Wave 74 deviations and open items
- Wave 75 deviations and open items
- Wave 76 deviations and open items
- Wave 77 deviations and open items
- Wave 79 deviations and open items
- Waves 80-81 deviations and open items
- Wave 82 deviations and open items
- Wave 83 deviations and open items
- TLS provider evaluation (Wave 84)
- Wave 85 deviations and open items
- Wave 86 deviations and open items
- Wave 87 deviations and open items
- TLS: rustls-webpki patch attempt (Wave 88)
- Wave 89 deviations and open items
- Wave 90 deviations and open items
- Wave 91 deviations and open items
- Wave 92 deviations and open items
- Wave 93 deviations and open items
- Wave 94 deviations and open items
- Wave 95 and 96 deviations and open items
- Wave 97 premise correction
- Wave 97 deviations and open items
- Wave 98 deviations and open items
- Wave 99 deviations and open items
- Wave 100 deviations and open items
- Wave 101: DTLS before selection (correction)
- Decision: TLS stays refused
- Wave 102: live broker checks
- Wave 103: parallel component ports (Haiku workers)
- Wave 103: Unix socket transport (pfunix)
- Wave 104: pfunix SOCK_SEQPACKET deferred
- Wave 104b: pfunix SOCK_SEQPACKET on Linux
- Wave 105: the Unix sockets transport runs from the binary
- Wave 106: session events reach event handlers (GELF)
- Wave 107: MQTT event handler runs from the binary
- Wave 108: sample HTTP event handler runs from the binary
- Wave 109: plugin events reach event handlers
- Wave 110: transport events reach event handlers
- Wave 111: WebSocket event handler runs from the binary
- Wave 112: RabbitMQ event handler runs from the binary
- Wave 113: HTTP request events; MQTT 5 user properties
- Wave 114: VideoRoom RTP forwarders
- Wave 115: VideoRoom simulcast forwarders
- Wave 116: VideoRoom data channel forwarders
- Wave 117: VideoRoom forwarder RTCP feedback
- Wave 118: VideoRoom forwarder events
- Wave 119: VideoRoom enable_recording
- Wave 120: media loop from peers through plugins and back
- Wave 121: RTCP to plugins; keyframe requests to peers
- Wave 122: plugin-owned UDP sockets
- Wave 123: VideoRoom remote publishers (add_remote_publisher)
- Wave 124: update and remove remote publishers
- Wave 125: VideoRoom remotization (publish_remotely, unpublish_remotely, listremotes)
- Wave 126: eight parallel ports
- Wave 127: RTCP relays and REMB from plugins
- Wave 128: RabbitMQ heartbeat checked live; message pool decision
- Wave 129: VideoRoom configure restart
- Wave 130: plugin socket release; relayed RTCP keeps its SSRCs
- Wave 131: ICE restart for plugin offers; every plugin offer is merged
- Wave 132: client answers to plugin offers, for ICE
- Wave 133: remote ICE restart on an incoming offer
- Wave 134: DTLS for plugin-initiated negotiation
- Wave 135: MQTT 5 checked live
- Wave 136: SCTP over DTLS for data channels
- Wave 137: data channels reach the plugin
- Wave 138: MQTT 5 takeover DISCONNECT on a real socket
- Wave 139: stream resets close data channels
- Wave 140: plugin data routes open a channel for a new label
- Wave 141: Opus through libopus bindings (ADR-0007)
- Wave 142: plugin gaps, five workers in parallel
- Wave 143: parity counters for the plugin and SIP waves
- Wave 144: counters for the SIP, lapin and Lua waves
- Wave 145: SIP re-INVITE, Lua gateway, Opus mono resolved
- Wave 146: Lua binary seam and recording, SIP peer SDP on autoaccept
- Wave 147: SIP re-INVITE 488 and incall, NoSIP PLI, AudioBridge limiter order, Recordplay and EchoTest recording
- Wave 148: RED mark, VideoRoom remote data, SIP setup SDP, Streaming VP9 simulcast
- Wave 149: Recordplay probe, VideoRoom forwarders, SIP setup payload types, Streaming VP8, core peer addressing
- Wave 150 (part 1): SIP re-INVITE SRTP and VideoRoom remotization SRTP
- Wave 150 (part 2): core PLI and close, EchoTest simulcast and SVC, TextRoom tests
- Wave 151: hangup events, Lua simulcast, session_of in Streaming and NoSIP, SIP update answer
- Wave 152: Lua recording layer, Streaming send path, Duktape host, AudioBridge announcements, VideoRoom remotization PLI
- Wave 153: Lua get overrides, Streaming data and PLI, VideoRoom remotized simulcast, Duktape pushEvent SDP, TextRoom binary wiring
- Wave 154: core data relay, VideoRoom remote substreams, SIP 2xx end-to-end, Recordplay data, AudioBridge room WAV
- Wave 155: VideoRoom remote offset and update, core relay gates, AudioBridge .mjr, SIP codec names and PLI, EchoTest rid
- Wave 156: VideoRoom audio codec and update notify, core ALERT send gates, recording pause and resume, Duktape recording, Recordplay data timing
- Wave 157: SIP recording pause and resume, VideoRoom remote stream fields, janus-record C playout policy, Duktape readFile correction
- Wave 158: SIP own recording start and stop, core flush-time gate, Streaming viewer PLI and keyframe burst
- Wave 159: SIP setup refusals, sequence reset, SRTP create failure, video orientation; EchoTest e2ee
- Wave 160: SIP inbound SRTP and relay header update; Streaming PLI on the source RTCP socket
- Wave 161: SIP peer forwarders and local SRTP, Duktape scheduler
- Wave 162: SIP stack forwarders and outbound protect, SIP offer a=crypto
- Waves 163 and 164: SIP peer SRTP and call wiring; Streaming VP8 and REMB; NoSIP logs; VideoRoom recorders; core hangup flags; janus-record temp name
- Wave 165: SIP update allocation, VideoCall answer media, Lua number text, Duktape error text, AudioBridge jitter pin
- Wave 166: janus-webrtc sender SSRC and stats, janus-rtp tables, janus-ice state rules, janus-sctp chunks
- Wave 167: TextRoom saver in the binary, MQTT 3.1 QoS 2, janus-sdp anonymize, janus-pp-rec inspection
- Wave 168: janus-core mid and webrtcup, SIP accept and forwarders, AudioBridge Opus, VideoRoom renegotiation, GELF TCP, janus-crypto tokens
- Wave 169: Recordplay, EchoTest, Streaming, NoSIP, Duktape and Lua audit
- Wave 170: binary event drain, rtpfwd base64, janus-record names, VideoCall names, SIP outbound cleanup, pp-rec convert
- Wave 171: AudioBridge groups, VideoRoom talk, ICE failure and RNG seam, RabbitMQ sequence, nanomsg PUB, SCTP tests
- Wave 174: limiter golden tests, script-js fidelity, rtpfwd base64 cases
- Wave 172: DTLS wire tests, webrtc NACK tracker, WebSocket log bits, pfunix and MQTT session routing
- Wave 173: janus-ice probe and lastsec, janus-rtp SVC and RTCP differentials, janus-json core events, GELF destroy, SIP helper sessions
- Waves 175 and 177: keyframe codec, RTX duplicate verdict, SCTP add-streams, text2pcap folders, RabbitMQ routing, plugin audit
- Waves 176 to 178 and 177 config: transport logs, binary event routes, AudioBridge and VideoCall fixes, opus encoder controls
- Waves 179 and 181: Recordplay simulcast record path, client answer keyframe codec, janus-record C policy, SCTP reset tests
- Wave 180: MQTT and pfunix transport log lines
- HTTP long-poll token check
- Wave 182: per-medium keyframe check, Lua and plugin info getters as Option
- Wave 183: per-medium RTP state
- Wave 184: SDP answer RTX and FID, RTCP SSRC matching, slow_link per medium
- Wave 185: SSRCs and the FID group on the answer
- Waves 186 and 187: plugin-offer SSRCs and msid, SIP incoming headers, ICE media events, per-medium webrtc counters
- Wave 188: core media-state wiring, plugin a=mid, SIP subscribe results
- Wave 189: VideoRoom persistence and dummy publisher; VideoCall list order; TWCC estimator blocked
- Wave 190: Streaming session resolution, HTTP connections counters
- Waves 191 and 192: local time, MQTT evh live test, AudioBridge forwarders, EchoTest lifecycle logs
- Wave 193: core offer refusal and single no-media source; event-handler audit
- Wave 194: SIP headers on the other events
- Wave 195: webrtc no-media check docs; SIP inbound REFER, NOTIFY and CANCEL
- Wave 196: SIP REFER decline and attach_call; NOTIFY on SUBSCRIBE dialogs
- Wave 197: SIP REFER final answer; MQTT 3.1 CONNECT flags and subscribe refusal; SCTP reset tests
- Wave 198: keyframe checks need a video packet; AudioBridge group forwarder UDP send
- Wave 199: Recordplay simulcast pins; Duktape RTP, RTCP and data hooks
- Wave 200: VideoRoom CSPRNG pins; core dispatch ordering pins for EchoTest and VideoCall
- Wave 201: peer addressing pins; ICE set_stun_server probe drop rule
- Wave 202: STUN probe socket and startup wrapper; TextRoom session delivery pin
- Wave 203: per-medium uplink slow_link; msid and a=mid pins
- Wave 204: core slow_link consumer; uplink sender report and receive gate
- Wave 205: session recv from the SDP direction; media stopped wired; default-medium slow_link
- Wave 206: recv gates incoming RTP; client answers re-apply recv
- Wave 207: incoming RTP routed by learned SSRC; the default medium sends no RR
- Wave 208: default medium sends no SR; SSRC learn before recv; RTX SSRC routes
- Waves 209 and 210: m-line identity kept across renegotiation; Streaming relay counts; RTP identification exposed
- Wave 211: Duktape default media path when no hook is defined
- Wave 212: pp-codecs G.722 gap WARN; nanomsg NN_PAIR socket I/O; HTTP missing credentials
- Wave 213: ICE role conflict and peer-reflexive learning; SRTP error strings; record no-filename; WebSocket listener headers
- Wave 214: DTLS certificate expiry and retry decision; nanomsg scheme refusal; RTCP loop after errors; pp-rec timing and logs
- Wave 215: MQTT both-APIs-disabled give-up; NoSIP lifecycle and SRTP lines; SCTP PPID doc
- Wave 216: DTLS wiring in janus-core; NAT startup options
- Wave 217: VideoRoom bounded id draws; core media dispatch pinned; AudioBridge room sender
- Wave 218: AudioBridge participant encoders and rtp_forward; core RTCP medium selection (not merged)
- Wave 219: SDP-declared SSRC seeding and RR sender from creation; SIP REFER send failure and helper credential
- Wave 220: peer SSRC routing rows; recv gate before SRTP unprotect
- Waves 221 to 224: renegotiation state; WebSocket reclaim; pp-rec Opus parsing; recordplay header; Duktape simulcast branch; utils H.264 and protected folders; streaming pin
- Waves 218 and 225 and 226: RTCP medium selection merged; WebSocket reclaim wiring; janus-rtp selectors
- Wave 228: pfunix reclaim window; claim arm session_over and session_claimed
- Wave 227: Duktape RTP relay to recipients
- Wave 229: Duktape simulcast pins; reclaim notices drained to HTTP
- Wave 230: admin and client destroy session_over; temporal hook log; pfunix timed-out client
- Wave 231: janus-record C pins; coverage of resolved rows
- Wave 232: Duktape hook error logs; client destroy queue line
- Wave 233: remaining Duktape hook error logs; scheduler waits for each call
- Wave 234: Duktape load-error pins
- Wave 236: parallel plugin pass; ICE nat_1_1 local address; TURN REST username
- Wave 237: Config and CLI options; Utilities and IP handling
- Waves 238 and 239: nanomsg enabled flags and created event; echotest slow_link logs; oracle checks in Docker
- Wave 240: Docker oracle captures; libconfig hex and NaN goldens
- Wave 236 counters: per-plugin rows from the parallel pass
- Waves 241 and 242: Docker captures; owner defaults applied (match C)
- Wave 243: SCTP matches usrsctp and sctp.c
- Wave 244: binary wiring; oracle scenarios; the differential runner (first end-to-end result)
- Wave 245: every captured scenario passes end to end
- Wave 246: SDP battery, media-plane loopback, janus-pp-rec muxers
- Wave 247: long tail (streaming media path, tools, event handlers, transports, low-level crates)
- Wave 248 (first half): RTSP and the janus-pp-rec video tail
- Wave 248 (second half): TLS, plugin request surfaces, Lua and Duktape, core settings
- Wave 249: ICE-TCP, SCTP probe, nanomsg wire, startup log and daemon, SIP live peer, Lua numbers