Other
- §4.2.1.3 PTYPE display-control flags (split-screen / doc-cam / freeze-release)
- §3.4 forced updating — per-MB cyclic INTRA refresh
- §4.2.3.4 MVD predictor reset at MB-row starts (MBA 12 / 23)
- criterion suite for §3.2.3 loop filter + §3.2.2 integer-pel MC
- RFC 4587 §4.2 MB-level fragmentation with §4.1 context
- §6.2.1 preference-aware a=fmtp formatter
- §6.2.1 strict-conformance
a=fmtpparser - §6.2.1 wire-order preference accessor for fmtp
- §6.2 strict-conformance accessor for RtpMap clock-rate MUST
- drop release-plz.toml — use release-plz defaults across the workspace
- criterion suite for §4.1 / §4.2 start-code scanner
- criterion suite for §5.4 BCH (511, 493) FEC layer
- §5.4.1 single-bit BCH (511,493) error correction (t = 1)
- still-image sub-image transform per H.261 §D.2 + §D.3
- fifth cargo-fuzz target for SDP signalling parser
- Annex A conformance test — §A.1..§A.9 against f64 reference
- fourth cargo-fuzz target for RTP data-path parser
- third cargo-fuzz target for BCH (511,493) FEC multiframe parser
Added
-
§4.2.1.3 PTYPE display-control flags (encoder). The encoder can now
emit the three picture-layer display-control bits the decoder already
parsed — split-screen indicator (bit 1), document-camera indicator
(bit 2), and freeze-picture release (bit 3, §4.3.3). A new
encoder::Ptypestruct carries the three flags and a new
encoder::write_picture_header_ptypewriter threads them into the
picture header;write_picture_header/write_picture_header_full
now delegate to it withPtype::default()(all flags off), so the
canonical motion-video header is byte-for-byte unchanged. Previously
these three bits were hardcoded to "0" with no caller-facing way to set
them. Four new round-trip tests assert each flag, and all three
together (on a CIF Annex-D still-image header to prove independence
from the source-format and HI_RES bits), reach the decoder's
parse_picture_headerexactly. -
§3.4 forced updating (per-MB cyclic INTRA refresh). H.261 §3.4
requires every macroblock to be forcibly INTRA-coded "at least once
per every 132 times it is transmitted" so that inverse-transform
mismatch error cannot accumulate without bound between whole-frame
I-refreshes. The encoder previously relied solely on the frame-level
intra_period(a whole-picture I-refresh). It now also runs a per-MB
forced-update scheduler:H261Encodertracks how many times each
macroblock has been transmitted since its last INTRA coding (global
raster order across all GOBs) and forces the due macroblocks to INTRA
mode inside a P-picture before any counter reaches the period. The
load is spread across frames with a round-robin sweep
(ceil(total_mbs / period)MBs per P-frame) instead of spiking when
every counter hits the cap together.H261Encoder::with_forced_update_period
overrides the period (default132, the spec maximum;0disables).
A new publicencode_inter_picture_forced_updatelets callers driving
the stateless P-picture path supply their own forced-update set (for
example the RFC 4587 §C.3 loss-driven MB refresh). INTRA MBs in a
P-picture reset the §4.2.3.4 MVD predictor since they are never
motion-compensated.
Fixed
- §4.2.3.4 MVD predictor reset at MB-row boundaries (MBA 12 and 23).
The decoder's motion-vector-data predictor only reset to zero at GOB
start, on MBA discontinuities, and when the previous MB was not
motion-compensated. It was missing §4.2.3.4 condition (1): the
predictor "is regarded as zero" for macroblocks 1, 12 and 23 (the
first MB of each of the three rows in an 11×3-MB GOB). MBA 1 was
already covered by the per-GOB context reset, but MBA 12 and 23 were
not — so a conformant stream carrying a non-zero MV at MB 11 (or 22)
immediately followed by a motion-compensated MB 12 (or 23) decoded
the wrong vector. The in-crate encoder had previously worked around
this by forcing the MV to zero at MBs 11 and 22 to keep the two
sides in agreement; with the decoder now spec-conformant, that
constraint is removed and the encoder may use motion compensation at
every MB. The RFC 4587 §4.2 MB-level fragmentation walker, which
carries its own §4.2.3.4 predictor tracking, was given the same
reset so it stays bit-for-bit in lockstep with the decoder. A new
sharedmb::mvd_predictorhelper is the single source of truth for
the three reset conditions, unit-tested across all of them.
Added
-
filter_mccriterion benchmark — §3.2.3 loop filter + §3.2.2
integer-pel motion-comp. The existingtransformbench covered
the inner (I)DCT, andencode/decodecover end-to-end picture
cost, but the two other per-block P-picture reconstruction
primitives the decoder runs on every coded P-block had no isolated
baseline. The newbenches/filter_mc.rstimesmb::apply_loop_filter
(the separable 1/4-1/2-1/4 in-loop filter with 0-1-0 edge taps) and
mb::copy_block_integer(the integer-pel reference fetch) across
three motion regimes (center,mv_nonzero,corner_clamp). Both
functions are nowpub(matching the existingfdct/idct
primitive exports) so an optimisation pass — a SIMD loop filter or a
branchless edge-clamp copy — has an A/B baseline distinct from the
transform numbers. Round-287 release-build aarch64 baseline: loop
filter ≈ 25 ns / block (≈ 2.5 Gelem/s); integer-pel copy ≈ 15 ns /
block (≈ 4.1 Gelem/s) interior, ≈ 14.5 ns fully corner-clamped. -
RFC 4587 §4.2 MB-level fragmentation. The RTP module previously
shipped only the "cheap" GOB-aligned packetizer; a GOB larger than
the payload budget was split at arbitrary byte boundaries with
zeroed context fields, so its continuation packets were not
independently decodable after a loss — the exact problem the §4.1
H.261 header exists to solve. The new
rtp::packetize_mb_fragmentedimplements the §4.2 RECOMMENDED
packetization: a single Huffman-layer walk over the elementary
stream (walk_mb_split_points— MBA/MTYPE/MQUANT/MVD/CBP/TCOEFF
VLCs parsed, nothing dequantised or transformed, per §4.2 "it is
not necessary to decompress the stream fully") records every legal
split point with its §4.1 context, then packets are filled greedily
(multiple GOBs/MBs per packet when they fit, per §3.2) under the
§3.2 rules: an MB is never split across packets, the stream is
never fragmented between a GOB header and MB 1, and no packet
crosses a PSC. Mid-GOB packets carry non-zero SBIT/EBIT plus the
GOBN / MBAP (biased -1) / QUANT / HMVD / VMVD context; the walker
tracks the §4.2.3.4 MV predictor (including the consecutive-MBA
and last-MB-was-MC rules) so the reference MVD is exact.
RtpPacketizer::with_mb_fragmentation(true)routespack_frame
through the new path with an automatic fallback to the byte-split
cheap packetizer when no MB-boundary split exists, and two new
RtpErrorvariants (MalformedStream,FragmentTooLarge) surface
walk/budget failures on the direct path. Eleven new tests cover
it: the Huffman-layer walk is checked bit-for-bit against a
real-decoder (decode_macroblock) oracle on I-pictures across a
quantiser sweep and on a P-picture with live motion vectors; round
trips at multiple budgets are byte-exact throughdepacketize
(which already handled non-zero SBIT/EBIT); fragment chains are
verified bit-contiguous (shared split byte,
next.SBIT == (8 - prev.EBIT) % 8); continuation headers are
matched back to walker split points; the PSC-crossing ban, the
whole-frame-in-one-packet case, theFragmentTooLargepath, and
two end-to-end RTP-session decodes (tests/rtp_e2e.rs) round it
out. -
RFC 4587 §6.2.1 preference-aware
a=fmtpformatter. §6.2.1
states "Parameters offered first are the most preferred picture
mode to be received" — an endpoint expresses its receive preference
purely through token order. The fixed-orderformat_value/
format_fmtpare locked to the §6.2.1 worked-example CIF-first
order, so an endpoint advertising both picture sizes but preferring
to receive QCIF could not express that on the wire. The new
H261FmtpParams::format_value_preferred(preferred)method and
format_fmtp_preferred(pt, ¶ms, preferred)free function emit
the preferred picture-size token first when that size is advertised
(QCIF=1;CIF=2;D=1for a QCIF-preferring endpoint), the other
advertised size second, andDlast (Dis an Annex-D codec
option, not a picture mode, so the §6.2.1 "offered first" rule does
not order it). A CIF preference is byte-identical to the canonical
formatter (format_valueis now a thin wrapper over
format_value_preferred(SourceFormat::Cif)); an unadvertised
preference falls back to the canonical order; the §6.2 "if any"
no-parameters ⇒ no-line rule is preserved. This is the emit-side
dual of the parse-sideparse_preference_orderaccessor: whenever
the params advertise the preferred size, the leading entry of
parse_preference_order(format_value_preferred(fmt))isfmt,
closing the §6.2.1 wire-order loop in both directions. Five new
unit tests insrc/sdp.rscover CIF-preference identity across
five parameter shapes, the QCIF-first emission + wire-order
read-back, the unadvertised-preference fallback (both directions),
the parse round trip under both preferences, and the full
a=fmtpline builder (QCIF-first line, CIF-preference byte
equality withformat_fmtp, empty-paramsNone, reparse through
parse_fmtp). The existing fuzz targetparse_sdp_fmtpand the
stable-CItests/fuzz_seed_corpus_sdp.rsdriver gain a Mode G
oracle: on every input that parses cleanly, (1)
format_value_preferred(Cif) == format_value(), (2) both
preference emissions reparse to equal params, and (3) when the
params advertise the preferred size,parse_preference_order
reads it back as the leading entry. New seed
fuzz/corpus/parse_sdp_fmtp/14_fmtp_value_both_sizes_d0.txt
carries a both-sizes +D=0bare parameter list. README's
SDP-media-type section is updated with the emit-side accessor. -
RFC 4587 §6.2.1 strict-conformance
a=fmtpparser. §6.2.1
states "Implementations following this specification SHALL specify
at least one supported picture size." The lenientparse_fmtp
deliberately preserves a well-formed-but-§6.2.1-violating line
(e.g.a=fmtp:31 D=1) so a caller that wants to recover the
parsedDvalue from a slightly-non-conformant peer can still see
what the wire said; the newparse_fmtp_strict(line, payload_type)
free function chains lenient parse +H261FmtpParams::validate()
so an SDP front end that wants to drop a §6.2.1-violating offer
can do so with one call. Mirrors the existingparse_rtpmap/
parse_rtpmap_strictpair on thea=rtpmapside of §6.2:
malformed parameter lists still surface asSome(Err(SdpError::…))
(the §6.2.1 picture-size check runs only after a successful parse)
so typed error propagation is preserved on the strict path; the
§6.2.1 RFC-2032 fallback (assume QCIF=1) is not silently
applied during strict validation because that fallback is a
negotiation rule, not an advertisement-validation rule (callers
that want it combineparse_fmtpwith
H261FmtpParams::rfc2032_fallback()themselves). Eight new unit
tests insrc/sdp.rscover the §6.2.1 worked example (strict ==
lenient), the empty-parameter-list rejection, theD=1-only
rejection, malformed-token error propagation, payload-type
mismatch, QCIF-only / CIF-only acceptance, "no fallback applied"
documentation, and a cross-cutting strict-implies-lenient sweep
on five §6.2.1-compliant lines. The existing fuzz target
parse_sdp_fmtpand the stable-CItests/fuzz_seed_corpus_sdp.rs
driver gain a Mode B oracle: every fuzz input is run through both
parse_fmtpandparse_fmtp_strict, asserting(Some(Ok(l)), Some(Ok(s)))⇒l == sands.validate().is_ok();(Some(Ok(l)), None)⇒l.validate() == Err(NoPictureSize);(Some(Err(le)), Some(Err(se)))⇒le == se; and the four impossible
combinations panic (strict succeeding where lenient fails, strict
dropping a parse error, or the two paths disagreeing on Ok/Err).
New seed
fuzz/corpus/parse_sdp_fmtp/13_fmtp_no_picture_size_strict_rejects.txt
carries the canonical "lenient accepts / strict rejects"
a=fmtp:31 D=1input. README's SDP-media-type section is updated
with the strict-parser accessor. -
RFC 4587 §6.2.1 wire-order preference accessor for
a=fmtp.
§6.2.1 says "Parameters offered first are the most preferred picture
mode to be received", and the spec's worked example
a=fmtp:31 CIF=2;QCIF=1;D=1advertises CIF first ⇒ CIF is the
preferred mode. The existing structuralpreferred_picture_size
accessor cannot distinguish that case from a peer that emits
QCIF=1;CIF=2(QCIF preferred) becauseH261FmtpParamsstores
cifandqcifas independentOption<u8>fields and discards
token order. The newH261FmtpParams::parse_preference_order(&str)
helper walks the same;-separated token listparse_valuedoes
and returnsVec<SourceFormat>in wire order (skipping unknown /
malformed / Annex-D tokens, deduplicating, and ignoring MPI range
so the helper can mine wire-order from a possibly-malformed offer).
The first entry, if any, is the peer's preferred mode per §6.2.1's
"offered first" rule. New unit tests insrc/sdp.rscover the §6.2.1
worked example, the QCIF-first variant, single-size advertisements,
the RFC 2032 fallback (empty list ⇒ caller substitutes QCIF=1),
case-insensitive parameter names, whitespace tolerance, deduplication,
malformed-token skipping, and the divergence from
preferred_picture_sizeon a QCIF-first input. The existing fuzz
targetparse_sdp_fmtpand the stable-CItests/fuzz_seed_corpus_sdp.rs
driver gain a Mode F oracle: wheneverparse_valuesucceeds, the
set of picture sizes reported byparse_preference_ordermust
equal the set ofSome(_)picture-size fields on the parsed params
(the two walkers see the same tokens), and the order list never
exceeds two entries. New seed
fuzz/corpus/parse_sdp_fmtp/12_fmtp_qcif_preferred_first.txt
carries the canonical QCIF-first wire-order input. README's
SDP-media-type section is updated with the wire-order accessor. -
RFC 4587 §6.2 strict-conformance accessor for
RtpMap. §6.2
states "The clock rate in thea=rtpmapline MUST be 90000."
parse_rtpmapdeliberately preserves the wireclock_rate
verbatim so a misbehaving peer can be diagnosed without losing the
parsed payload type; the typed accessorRtpMap::is_rfc4587_compliant
is the single-call check for "did the peer follow §6.2?", and the
newparse_rtpmap_strictfree function combines parse +
conformance into one call (returnsNonefor any well-formed but
non-90000 line, plus everythingparse_rtpmapalready rejects).
Two sweep tests insrc/sdp.rscover the §6.2 worked example
(8000and180000lenient-but-not-strict variants, an RFC 2032
rtpmap that happens to share §6.2's clock rate, the optional
third<params>field, payload-type and encoding-name rejections
inherited fromparse_rtpmap); the existing fuzz target
parse_sdp_fmtpand the stable-CItests/fuzz_seed_corpus_sdp.rs
driver now run a strict-vs-lenient oracle on every fuzz input
((Some(l), Some(s))⇒l == sands.is_rfc4587_compliant();
(Some(l), None)⇒!l.is_rfc4587_compliant(); the
"strict succeeds where lenient fails" case panics) so a future
regression in either path trips the daily fuzz run. New seed
fuzz/corpus/parse_sdp_fmtp/11_rtpmap_non_90000_clock_rate.txt
carries the canonical lenient-but-not-strict input
(a=rtpmap:31 H261/8000). README's "SDP media type and
rtpmap/fmtp parameters" section is updated with the strict accessor. -
Criterion bench for the §4.1 / §4.2 start-code scanner. New
benches/start_code.rsadds a fifth Criterion bench to the
round-175transform/encode/decode+ round-233bch
suite, covering the bit-by-bit scanner
(find_next_start_code_bits,find_next_start_code,
iter_start_codes) that sits on the inner loop of every
H261Decoder::send_packet, everyrtp::packetize_gob_aligned,
and the spec-mandated RFC 4587 §4rtp::depacketizesanity
check. Two groups:h261_start_code_iterwalks one full QCIF
I-frame (1 PSC + 3 GBSCs), one full CIF I-frame (1 PSC + 12
GBSCs), and three concatenated QCIF I-frames;h261_start_code_single
coversfind_next_start_codeon the first-byte-aligned PSC
(best case),find_next_start_code_bitsstarting at bit 3
(the §4.2 GOB-aligned packetizer's slow path when a GOB does
not land on a byte boundary), andfind_next_start_codeon a
pseudo-random 4 KiB buffer with every accidental0x0001window
flipped out (worst case — scan to end, returnNone, the cost
a misbehaving network endpoint can force on an RTP receiver per
fuzzed payload). Headline points on the round-238 baseline
(release, aarch64): the bit-by-bit scanner clocks ≈ 295–300 MiB/s
across all three full-stream walks (QCIF I-frame 15.1 µs at
295 MiB/s, CIF I-frame 60.2 µs at 297 MiB/s, three-QCIF 44.7 µs
at 300 MiB/s); a byte-aligned first-PSC hit costs ≈ 6 ns; a
3-bit-misaligned hit costs ≈ 18 ns; the worst-case 4 KiB no-hit
walk takes ≈ 13 µs (298 MiB/s). All inputs are synthesised
in-bench (encoder output for hit cases, xorshift + sweep-flip
for the no-hit case); no on-disk fixtures, no third-party tools,
nodocs/files at bench time.Cargo.tomlgains a fifth
[[bench]]entry (start_code,harness = false); the README's
### Benchmarkssection is updated with the new target's
sub-scenarios and headline numbers. An eventual SIMD pre-scan
over byte-aligned0x00 0x01candidates plus the bit-walk on
the few near-hit windows is the obvious follow-up; this bench
gives that change its A/B baseline. -
Criterion bench for the §5.4 BCH (511, 493) FEC layer. New
benches/bch.rsrounds out the round-175transform/encode/
decodeCriterion suite with the outer-coding hot path. Three
groups:bch_primitivescoversparity18andsyndrome18(the
493-bit shift-register long-division primitives, once each per
emitted / received §5.4.3 frame);bch_correctioncovers the
§5.4.1locate_single_errorwalk on a worst-case syndrome
(constructed locally asx^510 mod g(x)so the walk takes the
full 511 iterations before returningSome) and on an
uncorrectable syndrome (weight ≥ 2, returnsNoneafter the same
511 iterations);bch_multiframecovers the integrated
encode_multiframe/decode_multiframe/
decode_multiframe_with_correctionentry points on one full
8-frame §5.4.4 multiframe (clean + one-bit-corrupted variants).
Headline points on the round-233 baseline (release, aarch64):
parity18≈ 350 ns / frame (≈ 0.7 ns / data bit),syndrome18
≈ 346 ns / frame,locate_single_errorworst-case ≈ 460 ns and
uncorrectable ≈ 448 ns,encode_multiframe≈ 12.2 µs / multiframe,
decode_multiframeclean ≈ 24.5 µs,decode_multiframe
one-bit-corrupted ≈ 24.2 µs, and
decode_multiframe_with_correctionone-bit-corrupted ≈ 24.0 µs
(the §5.4.1 correcting decoder adds essentially no overhead over
the detection-only decoder when at most one frame in the
multiframe is corrupted — thelocate_single_errorwalk is dwarfed
by the per-frame syndrome work the decoder already does). All
inputs are synthesised in-bench from deterministic xorshift seeds;
no on-disk fixtures, no third-party tools, nodocs/files are
read at bench time.Cargo.tomlgains a fourth[[bench]]entry
(bch,harness = false); README's### Benchmarkssection is
updated with the new target's sub-scenarios and headline numbers. -
BCH (511,493) single-bit error correction (§5.4.1, t = 1). New
bch::locate_single_errormaps a non-zero 18-bit syndrome to the
corresponding 511-bit codeword position (wherep = 0isFi,
p = 1..493are the 492 data bits,p = 493..511are the 18 parity
bits) by walkingpow = x^i mod g(x)fori = 0..511and matching
the running power against the syndrome; a non-zero syndrome that
doesn't match any single-bit pattern reportsNone(the weight ≥ 2
case the t = 1 code cannot resolve). Newdecode_multiframe_with_correction
is the integrated path: same alignment-pattern lock as
decode_multiframe, pluslocate_single_erroris called on every
non-zero-syndrome frame and the indicated bit is flipped inside the
511-bit codeword before the per-frameFi/ data interpretation
runs.DecodedMultiframegains two new counters —corrected_frames
(subset ofcorrupted_framesthat were successfully corrected) and
uncorrectable_frames(complement: weight ≥ 2 patterns the code
cannot resolve). The detection-onlydecode_multiframepreserves its
prior behaviour and reportscorrected_frames = 0, uncorrectable_frames = 0. A sweep test insrc/bch.rs
(decode_with_correction_sweeps_every_protected_bit) walks every of
the 511 protected bit positions in a frame, flips one at a time,
decodes with correction, and verifies the recovered payload matches
the original bit-exact for every position. Two new integration tests
intests/bch_e2e.rsround-trip a real H.261 elementary stream
throughencode_multiframe→ single-bit corruption →
decode_multiframe_with_correction→H261Decoderand verify
PSNR_Y ≥ 32 dB after correction (matches the clean-channel
baseline), and confirm a two-bit error in the same frame is left in
uncorrectable_frameswith the breakdown
corrupted_frames == corrected_frames + uncorrectable_frames
internally consistent. Brings the BCH module to spec-mandated
capability: §5.4.1 explicitly labels the outer layer an "error
correcting code" andg(x) = (x^9 + x^4 + 1)(x^9 + x^6 + x^4 + x^3 + 1)
has minimum distanced = 3, supportingt = (d − 1) / 2 = 1
correction. Thelib.rsdoc-comment "Out of scope" entry that
previously deferred single-bit correction is removed; the new
out-of-scope entry covers only the multi-bit (weight ≥ 2) case the
code mathematically cannot resolve. -
Annex D still-image transmission helpers (§D.2 + §D.3). New
annex_dmodule wires Annex D into the codec without disturbing the
motion-video pipeline.SubImageIndexis the 0..=3 sub-image
identifier;still_image_trpacks it into the 5-bitTRfield with
the §D.3 invariants (low 2 bits = index, top 3 bits = 0), and
parse_still_image_trenforces them on the receive path.
still_image_dimensions/still_image_chroma_dimensionsderive the
§D.2 4× video-format sizes (QCIF ⇒ 352×288 still, CIF ⇒ 704×576).
subsample_still_imageimplements the Figure D.1 2:1 × 2:1 transform
(per-sub-image tile origin0→(0,0),1→(0,1),2→(1,1),
3→(1,0)) over Y/Cb/Cr planes;reassemble_still_imageis the
bit-exact inverse.PictureHeader::still_image_sub_indexreturns
Ok(None)for ordinary motion-video pictures and surfaces the §D.3
high-bits-must-be-zero violation when HI_RES=0 with malformed TR.
encoder::write_picture_header_fullis a new explicit-HI_RES variant
ofwrite_picture_header; the original 3-arg entry point is
preserved as a thin wrapper. Integration testtests/annex_d.rs
rounds-trips the picture header for every sub-image in both QCIF
and CIF mode and rounds-trips the sub-sample/reassemble transform
on full-size synthetic still images (luma + both chroma planes). -
Fifth cargo-fuzz target:
parse_sdp_fmtpdrives arbitrary
fuzz-supplied bytes through the H.261 Session Description Protocol
parser surface — the attribute-line parsers an endpoint runs on every
received SDP offer or answer at session setup before any RTP /
RTCP / H.261 layer sees a byte. Four entry points are exercised:
parse_rtpmap(RFC 4587 §6.2:a=rtpmap:<pt> H261/90000with
case-insensitive encoding-name match, payload-type and clock-rate
integer bounds; a non-H261encoding name surfaces asNone, an u8
payload-type overflow or u32 clock-rate overflow surfaces asNone),
parse_fmtp(RFC 4587 §6.2:a=fmtp:<pt> CIF=…;QCIF=…;D=…with
payload-type match),H261FmtpParams::parse_value(RFC 4587 §6.1.1:
semicolon-separated key=value list with CIF/QCIF MPI ∈ 1..=4 and
D ∈ {0,1} validation, duplicate-parameter rejection, forward-
compatible unknown-parameter skip), andnegotiate_answer(RFC 4587
§6.2.1 offer/answer rules: per-shared-sizemax(MPI)upper bound,
RFC 2032 QCIF=1 fallback for an offer with no picture size, both-
sides-required Annex-D survival). The harness decodes the fuzz input
as UTF-8 lossily once per iteration, drives each parser standalone,
splits the input on|and feeds the(offer, our)halves to the
negotiator, then runs a formatter → parser round trip on any input
that parses cleanly so aformat_value/parse_valuedisagreement
trips the daily run. Contract under test: every call must return
— no panic, no abort, no integer overflow (in debug / ASAN builds),
no out-of-bounds index, no allocator OOM. -
Seed corpus at
fuzz/corpus/parse_sdp_fmtp/(10 text buffers,
≈ 270 B total): the §6.2 worked-examplea=rtpmap:31 H261/90000and
a=fmtp:31 CIF=2;QCIF=1;D=1lines, a dynamic-payload-type rtpmap
(PT=96), a QCIF-only fmtp, a forward-compatible fmtp with an unknown
parameter, a lowercase-key fmtp (case-insensitive match per §6.1.1),
a|-split offer/our pair fornegotiate_answer, an empty-offer
pair that exercises the §6.2.1 RFC 2032 QCIF=1 fallback, a malformed
parameter list with every value out of range (CIF=5;QCIF=0;D=2),
and a non-H.261 rtpmap (H264/90000) the parser must reject. -
Stable-CI seed test
tests/fuzz_seed_corpus_sdp.rs(19 tests,
≈ 1 ms) mirrors the fuzz target on stable Rust so the regular CI
matrix catches a regression in the SDP signalling parser surface
without waiting for the daily fuzz run. Also drives empty /
single-zero / all-ones (non-UTF-8 → U+FFFD lossy decode) /
pseudo-random adversarial buffers, the §6.2 worked rtpmap + fmtp
round trips, a non-H.261 rtpmap rejection, an u8 payload-type
and u32 clock-rate overflow rejection onparse_rtpmap, a
payload-type mismatch onparse_fmtp, MPI-out-of-range /
Annex-D non-binary / duplicate-CIF / duplicate-QCIF / missing-=
rejections onparse_value, a forward-compatible unknown-parameter
skip, the §6.2.1 disjoint-advertisementNoPictureSizerejection,
the §6.2.1 RFC 2032 QCIF=1 fallback, the §6.2.1 both-sides Annex-D
rule, and a formatter → parser round-trip on the canonical
(CIF=4, QCIF=1, D=1)shape. -
Annex A IDCT accuracy conformance test (
tests/idct_annex_a.rs)
implements the §A.1..§A.9 measurable conformance procedure that ITU-T
Recommendation H.261 mandates for every compliant inverse 8×8 DCT.
The §A.1 deterministic 32-bit LCG (randx = randx * 1103515245 + 12345,
keep 30 bits LSB-cleared, divide by2^31, scale byL+H+1, truncate to
int, subtractL) generates 10 000 8×8 pel blocks per dataset; the §A.2
forward DCT and §A.4 reference IDCT run in 64-bit float directly from the
equations in §3.2.4 (no third-party IDCT source is consulted); §A.3 rounds
each transform coefficient to the nearest integer and clips it to
[-2048, +2047]to produce the 12-bit IDCT input; our in-crate
idct::idct_signedis run on the same input and clipped to[-256, +255]
per §A.5; the §A.7 statistics (per-pel peak error, per-pel MSE, overall
MSE, per-pel |mean error|, overall |mean error|) are asserted against
the spec thresholds (≤ 1, ≤ 0.06, ≤ 0.02, ≤ 0.015, ≤ 0.0015 respectively)
across all three §A.1 ranges(L=256, H=255),(L=H=5), and(L=H=300),
with the §A.9 sign-flipped rerun on each. §A.8 (all-zeros in produces
all-zeros out) and a smoke test on the §A.1 RNG round out the eight new
test cases. Measured margins on (L=256, H=255): peak=1 (limit ≤ 1),
per-pel MSE=1.0e-4 (limit ≤ 0.06), overall MSE=6.0e-6 (limit ≤ 0.02),
per-pel |mean|=1.0e-4 (limit ≤ 0.015), overall |mean|=3.0e-6 (limit
≤ 0.0015); (L=H=5) is bit-exact against the reference (peak=0). The
reference f64 DCT/IDCT used as the §A.4 comparison oracle are coded
directly from the §3.2.4 equation; no external library is consulted. -
Fourth cargo-fuzz target:
parse_rtp_payloaddrives arbitrary
fuzz-supplied bytes through the H.261 RTP data-path parser surface —
the network-receive parsers an endpoint runs on every received UDP
datagram before any H.261 bitstream layer sees a byte. Three
entry points are exercised:parse_rtp_fixed_header(RFC 3550 §5.1
— V / P / X / CC / M / PT / seq / ts / SSRC + 0..=15 CSRC entries,
bounds-checked against the attacker-controlled CC field, with V != 2
surfacing asFieldOverflowand an under-sized buffer surfacing as
ShortHeader),unpack_header(RFC 4587 §4.1 — the 4-byte H.261
RTP payload header with SBIT / EBIT / I / V / GOBN / MBAP / QUANT
/ HMVD / VMVD and the §4.1 sign-extension of the two 5-bit MV
deltas), anddepacketize(the multi-packet bit-walker that
honours per-packet SBIT/EBIT alignment and asserts the recovered
elementary stream contains at least one start code). The harness
carves the fuzz input into up to four syntheticH261RtpPayloads
with attacker-chosen header fields and attacker-chosen data
lengths, so the slow-path bit-walker, thepack_header/
unpack_headerround-trip, and the finaliter_start_codes
sanity check all stay covered against attacker-controlled bytes.
Contract under test: every call must return — no panic, no abort,
no integer overflow (in debug / ASAN builds), no out-of-bounds
index, no allocator OOM. -
Seed corpus at
fuzz/corpus/parse_rtp_payload/(9 buffers,
≈ 257 B total): a 12-byte fixed-header-only packet (CC=0, empty
payload), a 16-byte fixed-header + empty H.261 payload header
boundary, a V=2 CC=0 fixed header + GOB-aligned H.261 header +
faked PSC stream, the same shape with CC=3 and CC=15 CSRC lists, a
non-GOB-aligned H.261 header carrying SBIT=3 / EBIT=5 / I=V=1 /
GOBN=7 / MBAP=12 / QUANT=17 / HMVD=-7 / VMVD=11, a marker=0 mid-
frame packet, an adversarial V=1 datagram, and an adversarial
CC=15 buffer truncated at 12 bytes that must surface as
ShortHeader. -
Stable-CI seed test
tests/fuzz_seed_corpus_rtp.rs(10 tests,
≈ 10 ms) mirrors the fuzz target on stable Rust so the regular CI
matrix catches a regression in the RTP payload-parser surface
without waiting for the daily fuzz run. Also drives empty /
single-zero / all-ones / pseudo-random adversarial buffers, the
exact 12-byte fixed-header boundary, a CC=15 truncated input (must
returnShortHeader), a V=1 rejection (must return
FieldOverflow), apack_header↔unpack_headerround-trip on
the typical-fields header, and adepacketizeSBIT+EBIT-overflow
input that exercises the8 * data.len() - sbit - ebitempty-
payload branch. -
Third cargo-fuzz target:
decode_bch_multiframedrives arbitrary
fuzz-supplied bytes through the H.261 §5.4 BCH (511, 493) FEC
multiframe parser surface (decode_multiframe/parity18/
syndrome18). Covers the §5.4.4 lock-search candidate sweep (511
bit offsets × 24 framing-bit reads at a stride ofFRAME_BITS = 511), the §5.4.2 GF(2) generator-polynomial long-division
shift-register, and the §5.4.3 per-frameFi/ 492-data-bit /
18-parity-bit walk against attacker-controlled bytes. The target
also runs an error-injection mode: frames a deterministic
synthetic payload via the in-crateencode_multiframe, then uses
the fuzz input as a bit-flip vector to corrupt up to 16
attacker-chosen bit positions in the framed stream before
re-decoding — driving the non-zero-syndromecorrupted_frames
branch and (when a flip lands inside the 24-bit lock window) the
lock-loss path. Contract under test: every call must return — no
panic, no abort, no integer overflow (in debug / ASAN builds), no
out-of-bounds index, no allocator OOM. -
Seed corpus at
fuzz/corpus/decode_bch_multiframe/(9 buffers,
≈ 12.5 KB total): one and three multiframes of pure stuffing, three
multiframes of all-zeros / 0xC3 / pseudo-random payload, a payload-
then-fill mix that crosses theFi=1-to-Fi=0boundary, a single-
bit-flipped 3-multiframe stream, a 4-bit-prefix-shifted stream that
forces the lock-search pastbit0 = 0, a 2-multiframe input that
falls one frame short of the §5.4.4 lock window, and a 6-multiframe
0x5Apayload (twice the lock window). -
Stable-CI seed test
tests/fuzz_seed_corpus_bch.rs(9 tests,
≈ 10 ms) mirrors the fuzz target on stable Rust so the regular CI
matrix catches a regression in the BCH parser surface without
waiting for the daily fuzz run. Also drives empty / single-zero /
all-ones / pseudo-random adversarial buffers, a
one-byte-short-of-lock input, an attacker-chosen 32-bit parity
value, and a deterministic multi-bit injection that surfaces in
corrupted_frameswithout breaking lock.