Skip to content

v0.0.6

Choose a tag to compare

@MagicalTux MagicalTux released this 30 May 02:38
· 24 commits to master since this release
52bf31b

Other

  • RFC 4587 §6.2.1 offer/answer negotiation helper
  • fix truncated 1? TCOEFF prefix panic (daily-fuzz finding)
  • criterion suite for transform / encode / decode hot paths
  • scrub decorative external-implementation attribution
  • second cargo-fuzz target for RTCP compound parser
  • cargo-fuzz decoder harness + daily workflow

Added

  • SDP offer/answer negotiation helper (RFC 4587 §6.2.1). The
    sdp module gains the free function negotiate_answer(offer, our_capability) -> Result<H261FmtpParams, SdpError> that computes
    the §6.2.1 answer parameters from a received offer and our
    local capability:

    • Picture-size intersection. Only sizes both peers advertise
      survive into the answer; a disjoint pair (e.g. CIF-only offer vs
      QCIF-only capability) errors with SdpError::NoPictureSize,
      matching §6.2.1's "SHALL specify at least one supported picture
      size".
    • MPI per shared size. §6.1.1's MPI is the minimum picture
      interval
      , so 29.97 / MPI is the upper bound on frame rate.
      The answer carries MPI = max(offer.MPI, our.MPI) per shared
      size, i.e. the more restrictive bound binds.
    • Annex D (D). §6.2.1: "This option MUST NOT appear unless
      the sender of this SDP message is able to decode this option."
      The answer's D=1 requires both offer.d == Some(true) AND
      our_capability.d == Some(true); otherwise D is omitted from
      the answer (matching §6.1.1's "SHOULD NOT be used … if not
      supported").
    • RFC 2032 fallback. §6.2.1: "If the receiver does not specify
      the picture size/MPI parameter … assume that such a receiver is
      able to support reception of QCIF resolution with MPI=1." The
      helper applies that fallback automatically (equivalent to
      H261FmtpParams::rfc2032_fallback()) when the offer carries no
      picture-size parameter. The fallback is not applied to
      our_capability — that side is local and should be supplied
      explicitly.

    The companion method H261FmtpParams::preferred_picture_size()
    returns the preferred receiver mode per §6.2.1 ("Parameters offered
    first are the most preferred") — Some(SourceFormat::Cif) when CIF
    is advertised (matching format_value's CIF-before-QCIF emission
    order from the §6.2.1 worked example), Some(SourceFormat::Qcif)
    when only QCIF is, else None. Eight new tests cover the
    intersection / MPI-max / disjoint-sizes / Annex-D / RFC-2032-
    fallback / format round-trip / max-frame-rate / validate-passes
    paths; the negotiation example also runs as a doctest.

Fixed

  • Decoder panic on a truncated 1? TCOEFF prefix (round 175,
    surfaced by the scheduled daily decode_h261 fuzz harness).
    decode_tcoeff(.., is_first = false) saw a bit-reader where exactly
    one bit remained and that bit was 1. The function took the
    b0 == 1 branch and then peeked two bits from
    peek >> (avail - 2), where avail = 1 caused an unsigned
    underflow → attempt to subtract with overflow panic under debug
    / ASAN builds. The two-bit peek is now gated behind avail >= 2
    and the call returns Error::invalid("h261 tcoeff: truncated 1? prefix") on the malformed input, restoring the public-surface
    contract from the fuzz harness: every call returns — no panic, no
    abort, no out-of-bounds. New regression test
    tcoeff_truncated_one_bit_does_not_panic covers it on stable Rust.

Added

  • Criterion benchmark suite (benches/transform, benches/encode,
    benches/decode). Round 175 (depth-mode) wires up criterion = "0.5"
    as a dev-dependency and registers three harness = false bench
    binaries so future optimisation rounds have a recorded baseline to
    A/B against:

    • transform times the 8×8 inverse / forward DCT block hot path —
      fdct_intra + fdct_signed (encoder forward pass) and
      idct_intra + idct_signed (decoder inverse pass). One block per
      iteration; throughput reported in samples so per-sample cycle-
      equivalents land naturally.
    • encode times whole-picture encode through the production
      encode_intra_picture / H261Encoder::encode_frame paths in four
      scenarios: QCIF intra-only (no ME), single-P from a pre-built I
      reference, I + 3 P chain (full rate-controller carryover), and CIF
      intra (the 4× area test).
    • decode times whole-picture decode through H261Decoder::send_packet
      • receive_frame, mirroring the encode scenarios. Each decode
        bench runs the in-crate encoder once during setup to produce a
        real elementary stream, so the timed loop measures the decoder
        alone.
        Every benchmark synthesises its YUV source inline from a
        deterministic striped pattern plus low-amplitude xorshift noise —
        no on-disk fixtures, no third-party CLI, no docs/ files read at
        bench time. cargo bench -p oxideav-h261 --no-run doubles as a
        compile-only CI regression guard via the existing matrix.
  • Second cargo-fuzz target — RTCP compound parser. New
    parse_rtcp_compound fuzz target drives arbitrary fuzz-supplied bytes
    through the public RTCP parser surface (parse_compound,
    parse_report, parse_sdes, parse_bye, parse_app) so the §6.1
    compound walk (16-bit-length advance), the SR/RR fixed header + RC
    block walk, SDES chunk + item walk (including the PRIV inner 8-bit
    length), BYE reason-string length-prefix, and APP name/data 32-bit
    alignment are all exercised against bytes whose shape the fuzzer
    dictates. Same contract as the existing decode_h261 target: every
    call must return — no panic, no abort, no integer overflow (in debug
    / ASAN builds), no out-of-bounds index, no allocator OOM. The seed
    corpus under fuzz/corpus/parse_rtcp_compound/ contains nine valid
    datagrams (empty RR, SR with no blocks, SR with one block, RR with
    two blocks, SDES CNAME, BYE with reason, APP with PING payload, and
    two compound packets). tests/fuzz_seed_corpus_rtcp.rs drives the
    same logic on stable Rust against the corpus plus several adversarial
    in-line buffers (lying header length, zero-length advance, truncated
    compound, SDES PRIV length overflow, BYE reason overflow, APP at the
    5-bit subtype maximum, unknown PT=205) so a regression in the public
    parser surface trips an existing CI lane rather than waiting for the
    daily fuzz run.