rtmp-runtime 0.6.0

Sans-IO RTMP 1.0 (Adobe Real-Time Messaging Protocol) ingest session engine — a driveable server-side publish state machine over the handshake, chunk stream, AMF0, and message layers; optional tokio socket adapter.
Documentation
# Changelog

All notable changes to `rtmp-runtime` are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.6.0] - 2026-08-11

### Changed
- MSRV raised to **1.95.0** (issue #949). This removes the workspace's MSRV
  split: `webrtc-runtime`'s optional `media` feature needed rustc 1.88 (via
  `rcgen`), which had grown a dedicated CI job, six `--exclude` lanes and a
  guard script to contain. Adopting let-chains and `is_multiple_of` where the
  1.95 lints require them; no functional or API change.

## [0.5.0] - 2026-08-07

### Added
- `client` module — sans-IO RTMP 1.0 client publish session engine
  (`ClientSession`, `ClientHandshake`, `ClientConfig`, `ClientEvent`):
  `connect``createStream``publish` auto-advance,
  `send_audio()`/`send_video()`/`send_metadata()` for the Publishing state
  (issue #744).

## [0.4.0] - 2026-08-05

### Changed
- Requires `transmux` 0.23 (epoch-pure caret bump from ^0.21; `TrackSpec`
  is part of this crate's public ingest API).

## [0.3.0] - 2026-07-30

### Changed (Breaking)
- `LimitType`, `Fmt`, `MessageHeader` (`chunk`, `message`) now carry
  `#[non_exhaustive]` (issue #806's non_exhaustive drift-guard audit). A
  downstream `match` on any of these now needs a wildcard arm.

### Added
- `tests/non_exhaustive_coverage.rs` drift guard (issue #806).

## [0.2.0] - 2026-07-29

### Changed (BREAKING)
- **Requires `broadcast-common` 9.** No functional or API change of this
  crate's own; the bump exists solely to carry the new requirement.
  `broadcast-common` 9.0.0 changed `Encrypt::encrypt` to take `&mut self` (so a
  stateful implementor can own a running per-key IV counter — it fixes a
  duplicate-IV/two-time-pad defect), and its `Parse`/`Serialize` traits appear
  in this crate's public API, so a consumer cannot mix a `broadcast-common` 8
  build with this one. That makes it a breaking release even though no line of
  logic here moved.

## [0.1.0] - 2026-07-26

### Added
- `examples/capture_publish.rs` (feature `tokio`): a real-socket RTMP publish
  recorder driving `ServerSession` over a live TCP connection, used to
  capture `tests/fixtures/obs-publish.bin` (a real `ffmpeg -f flv` publish).
- `tests/ingest_fixture.rs`: replays the captured fixture through
  `ServerSession::handle_data` (single-call and small-chunk reassembly) and
  feeds the emitted FLV to `transmux::FlvDemux`, asserting a decoded
  H.264+AAC `Media` — the real-fixture end-to-end ingest test.
- `serde` feature now actually derives `Serialize`/`Deserialize` on the owned
  public wire/event types (`ServerEvent`, `ServerConfig`, `Amf0Value`,
  `Command`), with a round-trip test gated on the feature.
- `ServerConfig::with_expected_stream_key`/`with_chunk_size`/
  `with_window_ack_size`/`with_peer_bandwidth` builder methods, needed now
  that `ServerConfig` is `#[non_exhaustive]`.

### Fixed
- **Remote excessive-allocation DoS**: `ChunkAssembler` no longer
  `Vec::with_capacity`s an inbound `message_length` (a fully
  attacker-controlled 24-bit wire field, up to ~16 MiB) before a single
  payload byte has arrived for it — the buffer instead grows incrementally
  as real chunk payload shows up. Added `MAX_MESSAGE_LEN` (8 MiB): a
  Type 0/1 header declaring a larger `message_length` is rejected before
  any buffer is allocated. Added `MAX_CSIDS` (64): a flood of chunks opening
  more than this many distinct, previously-unseen chunk stream ids is
  rejected rather than growing the per-csid state map without bound.
- `ServerSession::handle_data` now dispatches each reassembled message as
  soon as it is parsed, instead of collecting a whole `ChunkAssembler::push`
  batch before dispatching any of them. A client Set Chunk Size (§5.4.1)
  arriving in the same `handle_data` call as chunks already framed at the
  new size — exactly what a real `ffmpeg` publisher does — was previously
  misparsed, since the old chunk size stayed in effect for the rest of that
  batch. Caught by the real `ffmpeg` capture fixture above.
- `ChunkAssembler` gained crate-internal `feed`/`next_message` (incremental,
  one-message-at-a-time parsing) alongside the existing `push`, which
  `ServerSession` now uses for this reason.
- `ChunkAssembler`/`ChunkWriter::set_chunk_size` now also cap the negotiated
  chunk size at `MAX_CHUNK_SIZE` (16 MiB), in addition to the existing
  floor-of-1.
- `Amf0Value`, `ProtocolControl`, and `UserControl` are now `#[non_exhaustive]`
  (each models a documented subset of its spec catalogue).
- `read_utf8_short`/`read_utf8_long` (AMF0 String/Long String length
  prefixes) now use `checked_add` instead of a bare `+` when computing the
  total consumed length, guarding against `usize` overflow on 32-bit
  targets.
- `ServerSession`'s `next_stream_id` counter now uses `saturating_add`
  instead of a bare `+= 1`.