Skip to content

v0.0.7

Latest

Choose a tag to compare

@MagicalTux MagicalTux released this 15 Jun 05:15
· 5 commits to master since this release
0140fec

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=fmtp parser
  • §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::Ptype struct carries the three flags and a new
    encoder::write_picture_header_ptype writer threads them into the
    picture header; write_picture_header / write_picture_header_full
    now delegate to it with Ptype::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_header exactly.

  • §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: H261Encoder tracks 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 (default 132, the spec maximum; 0 disables).
    A new public encode_inter_picture_forced_update lets 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
    shared mb::mvd_predictor helper is the single source of truth for
    the three reset conditions, unit-tested across all of them.

Added

  • filter_mc criterion benchmark — §3.2.3 loop filter + §3.2.2
    integer-pel motion-comp.
    The existing transform bench covered
    the inner (I)DCT, and encode / decode cover 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 new benches/filter_mc.rs times mb::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 now pub (matching the existing fdct / 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_fragmented implements 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) routes pack_frame
    through the new path with an automatic fallback to the byte-split
    cheap packetizer when no MB-boundary split exists, and two new
    RtpError variants (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 through depacketize
    (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, the FragmentTooLarge path, and
    two end-to-end RTP-session decodes (tests/rtp_e2e.rs) round it
    out.

  • RFC 4587 §6.2.1 preference-aware a=fmtp formatter. §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-order format_value /
    format_fmtp are 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, &params, preferred) free function emit
    the preferred picture-size token first when that size is advertised
    (QCIF=1;CIF=2;D=1 for a QCIF-preferring endpoint), the other
    advertised size second, and D last (D is 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_value is 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-side parse_preference_order accessor: whenever
    the params advertise the preferred size, the leading entry of
    parse_preference_order(format_value_preferred(fmt)) is fmt,
    closing the §6.2.1 wire-order loop in both directions. Five new
    unit tests in src/sdp.rs cover 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=fmtp line builder (QCIF-first line, CIF-preference byte
    equality with format_fmtp, empty-params None, reparse through
    parse_fmtp). The existing fuzz target parse_sdp_fmtp and the
    stable-CI tests/fuzz_seed_corpus_sdp.rs driver 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=0 bare parameter list. README's
    SDP-media-type section is updated with the emit-side accessor.

  • RFC 4587 §6.2.1 strict-conformance a=fmtp parser. §6.2.1
    states "Implementations following this specification SHALL specify
    at least one supported picture size." The lenient parse_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
    parsed D value from a slightly-non-conformant peer can still see
    what the wire said; the new parse_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 existing parse_rtpmap /
    parse_rtpmap_strict pair on the a=rtpmap side of §6.2:
    malformed parameter lists still surface as Some(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 combine parse_fmtp with
    H261FmtpParams::rfc2032_fallback() themselves). Eight new unit
    tests in src/sdp.rs cover the §6.2.1 worked example (strict ==
    lenient), the empty-parameter-list rejection, the D=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_fmtp and the stable-CI tests/fuzz_seed_corpus_sdp.rs
    driver gain a Mode B oracle: every fuzz input is run through both
    parse_fmtp and parse_fmtp_strict, asserting (Some(Ok(l)), Some(Ok(s)))l == s and s.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=1 input. 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=1 advertises CIF first ⇒ CIF is the
    preferred mode. The existing structural preferred_picture_size
    accessor cannot distinguish that case from a peer that emits
    QCIF=1;CIF=2 (QCIF preferred) because H261FmtpParams stores
    cif and qcif as independent Option<u8> fields and discards
    token order. The new H261FmtpParams::parse_preference_order(&str)
    helper walks the same ;-separated token list parse_value does
    and returns Vec<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 in src/sdp.rs cover 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_size on a QCIF-first input. The existing fuzz
    target parse_sdp_fmtp and the stable-CI tests/fuzz_seed_corpus_sdp.rs
    driver gain a Mode F oracle: whenever parse_value succeeds, the
    set of picture sizes reported by parse_preference_order must
    equal the set of Some(_) 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 the a=rtpmap line MUST be 90000."
    parse_rtpmap deliberately preserves the wire clock_rate
    verbatim so a misbehaving peer can be diagnosed without losing the
    parsed payload type; the typed accessor RtpMap::is_rfc4587_compliant
    is the single-call check for "did the peer follow §6.2?", and the
    new parse_rtpmap_strict free function combines parse +
    conformance into one call (returns None for any well-formed but
    non-90000 line, plus everything parse_rtpmap already rejects).
    Two sweep tests in src/sdp.rs cover the §6.2 worked example
    (8000 and 180000 lenient-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 from parse_rtpmap); the existing fuzz target
    parse_sdp_fmtp and the stable-CI tests/fuzz_seed_corpus_sdp.rs
    driver now run a strict-vs-lenient oracle on every fuzz input
    ((Some(l), Some(s))l == s and s.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.rs adds a fifth Criterion bench to the
    round-175 transform / encode / decode + round-233 bch
    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, every rtp::packetize_gob_aligned,
    and the spec-mandated RFC 4587 §4 rtp::depacketize sanity
    check. Two groups: h261_start_code_iter walks 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
    covers find_next_start_code on the first-byte-aligned PSC
    (best case), find_next_start_code_bits starting at bit 3
    (the §4.2 GOB-aligned packetizer's slow path when a GOB does
    not land on a byte boundary), and find_next_start_code on a
    pseudo-random 4 KiB buffer with every accidental 0x0001 window
    flipped out (worst case — scan to end, return None, 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,
    no docs/ files at bench time. Cargo.toml gains a fifth
    [[bench]] entry (start_code, harness = false); the README's
    ### Benchmarks section is updated with the new target's
    sub-scenarios and headline numbers. An eventual SIMD pre-scan
    over byte-aligned 0x00 0x01 candidates 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.rs rounds out the round-175 transform / encode /
    decode Criterion suite with the outer-coding hot path. Three
    groups: bch_primitives covers parity18 and syndrome18 (the
    493-bit shift-register long-division primitives, once each per
    emitted / received §5.4.3 frame); bch_correction covers the
    §5.4.1 locate_single_error walk on a worst-case syndrome
    (constructed locally as x^510 mod g(x) so the walk takes the
    full 511 iterations before returning Some) and on an
    uncorrectable syndrome (weight ≥ 2, returns None after the same
    511 iterations); bch_multiframe covers the integrated
    encode_multiframe / decode_multiframe /
    decode_multiframe_with_correction entry 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_error worst-case ≈ 460 ns and
    uncorrectable ≈ 448 ns, encode_multiframe ≈ 12.2 µs / multiframe,
    decode_multiframe clean ≈ 24.5 µs, decode_multiframe
    one-bit-corrupted ≈ 24.2 µs, and
    decode_multiframe_with_correction one-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 — the locate_single_error walk 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, no docs/ files are
    read at bench time. Cargo.toml gains a fourth [[bench]] entry
    (bch, harness = false); README's ### Benchmarks section 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_error maps a non-zero 18-bit syndrome to the
    corresponding 511-bit codeword position (where p = 0 is Fi,
    p = 1..493 are the 492 data bits, p = 493..511 are the 18 parity
    bits) by walking pow = x^i mod g(x) for i = 0..511 and matching
    the running power against the syndrome; a non-zero syndrome that
    doesn't match any single-bit pattern reports None (the weight ≥ 2
    case the t = 1 code cannot resolve). New decode_multiframe_with_correction
    is the integrated path: same alignment-pattern lock as
    decode_multiframe, plus locate_single_error is called on every
    non-zero-syndrome frame and the indicated bit is flipped inside the
    511-bit codeword before the per-frame Fi / data interpretation
    runs. DecodedMultiframe gains two new counters — corrected_frames
    (subset of corrupted_frames that were successfully corrected) and
    uncorrectable_frames (complement: weight ≥ 2 patterns the code
    cannot resolve). The detection-only decode_multiframe preserves its
    prior behaviour and reports corrected_frames = 0, uncorrectable_frames = 0. A sweep test in src/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
    in tests/bch_e2e.rs round-trip a real H.261 elementary stream
    through encode_multiframe → single-bit corruption →
    decode_multiframe_with_correctionH261Decoder and 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_frames with 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" and g(x) = (x^9 + x^4 + 1)(x^9 + x^6 + x^4 + x^3 + 1)
    has minimum distance d = 3, supporting t = (d − 1) / 2 = 1
    correction. The lib.rs doc-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_d module wires Annex D into the codec without disturbing the
    motion-video pipeline. SubImageIndex is the 0..=3 sub-image
    identifier; still_image_tr packs it into the 5-bit TR field with
    the §D.3 invariants (low 2 bits = index, top 3 bits = 0), and
    parse_still_image_tr enforces them on the receive path.
    still_image_dimensions / still_image_chroma_dimensions derive the
    §D.2 4× video-format sizes (QCIF ⇒ 352×288 still, CIF ⇒ 704×576).
    subsample_still_image implements the Figure D.1 2:1 × 2:1 transform
    (per-sub-image tile origin 0→(0,0), 1→(0,1), 2→(1,1),
    3→(1,0)) over Y/Cb/Cr planes; reassemble_still_image is the
    bit-exact inverse. PictureHeader::still_image_sub_index returns
    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_full is a new explicit-HI_RES variant
    of write_picture_header; the original 3-arg entry point is
    preserved as a thin wrapper. Integration test tests/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_fmtp drives 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/90000 with
    case-insensitive encoding-name match, payload-type and clock-rate
    integer bounds; a non-H261 encoding name surfaces as None, an u8
    payload-type overflow or u32 clock-rate overflow surfaces as None),
    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), and negotiate_answer (RFC 4587
    §6.2.1 offer/answer rules: per-shared-size max(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 a format_value / parse_value disagreement
    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-example a=rtpmap:31 H261/90000 and
    a=fmtp:31 CIF=2;QCIF=1;D=1 lines, 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 for negotiate_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 on parse_rtpmap, a
    payload-type mismatch on parse_fmtp, MPI-out-of-range /
    Annex-D non-binary / duplicate-CIF / duplicate-QCIF / missing-=
    rejections on parse_value, a forward-compatible unknown-parameter
    skip, the §6.2.1 disjoint-advertisement NoPictureSize rejection,
    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 by 2^31, scale by L+H+1, truncate to
    int, subtract L) 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_signed is 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_payload drives 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 as FieldOverflow and 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), and depacketize (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 synthetic H261RtpPayloads
    with attacker-chosen header fields and attacker-chosen data
    lengths, so the slow-path bit-walker, the pack_header/
    unpack_header round-trip, and the final iter_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
    return ShortHeader), a V=1 rejection (must return
    FieldOverflow), a pack_headerunpack_header round-trip on
    the typical-fields header, and a depacketize SBIT+EBIT-overflow
    input that exercises the 8 * data.len() - sbit - ebit empty-
    payload branch.

  • Third cargo-fuzz target: decode_bch_multiframe drives 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 of FRAME_BITS = 511), the §5.4.2 GF(2) generator-polynomial long-division
    shift-register, and the §5.4.3 per-frame Fi / 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-crate encode_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-syndrome corrupted_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 the Fi=1-to-Fi=0 boundary, a single-
    bit-flipped 3-multiframe stream, a 4-bit-prefix-shifted stream that
    forces the lock-search past bit0 = 0, a 2-multiframe input that
    falls one frame short of the §5.4.4 lock window, and a 6-multiframe
    0x5A payload (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_frames without breaking lock.