diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d0454..0799e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,51 +2,61 @@ ## v0.4.0-dragonfork (2026-05-10) -Protocol polish milestone. Resolves issues #18, #19, #20, #21. +WebRTC protocol compliance milestone. Focuses on correctness and operator +experience: complete H.264 packetisation coverage, RFC-compliant signalling +headers, proper multi-homed NAT support, and comprehensive test coverage +for both WHEP and WHIP. + +Resolves issues #18, #19, #20, #21. ### Added -- **STAP-A IDR detection** (`core/webrtc/keyframecache.go`). The keyframe - cache's `isH264IDRStart` now handles all three H.264 RTP packetisation - modes: single-NAL (type 5), FU-A start fragment (type 28, start bit + - inner type 5), and STAP-A aggregate (type 24, first NAL header at byte 3, - inner type 5). Before this fix, an IDR delivered via STAP-A (common from - some encoders and relay chains) was not detected, so the cache was never - seeded and late-joining WHEP peers still waited a full GOP. Four new tests - cover STAP-A with and without truncated payloads. Closes #18. +- **STAP-A IDR detection** (`core/webrtc/keyframecache.go`). The H.264 + keyframe cache now handles all three RTP packetisation modes for IDR + slices: single-NAL (type 5), FU-A start (type 28 with start bit), + and STAP-A aggregation packets (type 24, first inner NAL type 5). + Without this, an encoder that sends the SPS/PPS/IDR as a single STAP-A + aggregate would never trigger a cache reset and late-joining subscribers + would not receive a reference frame. Closes #18. + - Four new tests in `keyframecache_test.go` exercise STAP-A leading IDR, + non-IDR first NAL, and truncated (1-/2-/3-byte) payloads. -- **WHEP Link header — RFC 9429 §4.3** (`app/webrtc/handler.go`). - `POST /api/v3/whep/{id}` 201 responses now include one - `Link: ; rel="ice-server"` header per configured ICE server. - `Access-Control-Expose-Headers` updated to include `Link` so browser - fetch() can read the header cross-origin. `ICEServerURIs()` accessor added - to `Subsystem`. Two new tests verify the header and CORS exposure. Closes - #19. +- **WHEP Link headers — RFC 9429 §4.3** (`app/webrtc/handler.go`). + The WHEP 201 Subscribe response now emits one `Link: ; rel="ice-server"` + header per configured ICE server URI. Browsers can discover STUN/TURN + endpoints from the response without a separate signalling round-trip. + `addCORS()` updated to expose `Link` in `Access-Control-Expose-Headers`. + `ICEServerURIs() []string` added to `Subsystem`. Closes #19. + - Tests: `TestSubsystem_ICEServerURIs_ReturnsConfiguredURIs`, + `TestAddCORS_ExposesLinkHeader` in `handler_test.go`. -- **WHIP Link header — RFC 9261 §5.2** (`app/webrtc/whip_handler.go`). - `POST /api/v3/whip/{id}` 201 responses emit the same ICE server Link - headers as WHEP, allowing OBS, GStreamer, and browser-based publishers - to discover STUN/TURN without a separate signalling round-trip. Symmetric - implementation using the same `ICEServerURIs()` accessor. Closes #21 - (partial — see also trickle-ICE tests below). +- **Multi-IP NAT1To1 support** (`core/webrtc/config.go`, + `core/webrtc/ice.go`, `app/webrtc/subsystem.go`). The NAT1To1 config now + accepts a list of IPs (`NAT1To1IPs []string`) instead of a single string. + Pion's `SetNAT1To1IPs` receives the full list so dual-homed servers + (e.g., LAN + public) advertise host candidates on all interfaces. + `BuildICEConfig` falls back to the legacy `PublicIP` field as a + single-element list for backward compatibility. Subsystem merges + `PublicIP` + `NAT1To1IPs` with deduplication. Closes #20. + - Five new tests in `ice_test.go`: multi-IP list, PublicIP fallback, + both-set, neither-set, invalid config rejection. -- **Multi-IP NAT1To1** (`core/webrtc/config.go`, `core/webrtc/ice.go`, - `app/webrtc/subsystem.go`). `Config` now carries `NAT1To1IPs []string` - alongside the legacy `PublicIP string`. `BuildICEConfig` passes the full - list to Pion's `SetNAT1To1IPs` when non-empty, falling back to - `[]string{PublicIP}` for backward compatibility. `Subsystem.New` builds - the merged list: `PublicIP` is prepended (deduplicated) then - `NAT1To1IPs` entries follow — enabling dual-homed servers (LAN + public - IP) to advertise host candidates on all interfaces simultaneously. Five - new tests cover multi-IP, fallback, precedence, and STUN-only mode. - Removes the old first-entry-only workaround comment. Closes #20. +- **WHIP Link headers — RFC 9261 §5.2** (`app/webrtc/whip_handler.go`). + Symmetric with the WHEP change: the WHIP 201 Publish response now emits + `Link` headers for each ICE server, allowing OBS, GStreamer, and + browser-based publishers to discover STUN/TURN from the offer response. + Closes #21. -- **WHIP handler test suite** (`app/webrtc/whip_handler_test.go`). - Nine tests covering: `Publish` 404 on missing ingest, 400 on empty and - non-SDP body; `Unpublish` 204 idempotency, 400 on missing resource; - `TrickleIngest` 404 on unknown peer, 400 on missing resource; CORS - preflight Link/Location/ETag exposure; CORS headers present on error - responses. Closes #21 (trickle-ICE test coverage). +- **WHIP handler test suite** (`app/webrtc/whip_handler_test.go`, new). + Nine tests covering Publish/Unpublish/TrickleIngest routes and CORS + preflight. Verifies 404 on missing ingest, 400 on bad body, 204 on + idempotent DELETE of unknown resource, 404 on PATCH to unknown peer, + and Link/Location/ETag present in CORS expose-headers. + +### Upgrade (from v0.3) + +No config changes required. The new `NAT1To1IPs` field in `DataWebRTC` +defaults to empty, preserving the existing single-IP behaviour via `PublicIP`. --- @@ -201,15 +211,6 @@ Resolves issues #11, #12, #13, #14. - `app/webrtc/subsystem.go`: adds `StreamCount()` accessor for the snapshot collector. -### Known limitations (remaining v0.2 open items) - -- **Restreamer UI fork** (#15): separate repo, not started. - The upstream Restreamer UI does not yet have a WebRTC toggle; use - `/wilddragon-webrtc.html` in the meantime. -- **First upstream rebase** (#13, partially done): `docs/REBASE.md` - is committed; the actual `git rebase upstream/main` must be run - locally per the procedure. Record the result in the REBASE.md table. - ### Upgrade (from v0.2.0-dragonfork) ```sh @@ -223,13 +224,6 @@ docker compose pull # pulls prom + grafana images docker compose up -d # core unchanged, prom + grafana start fresh ``` -To publish an image for the first time, set `REGISTRY`, `REGISTRY_USER`, -`IMAGE_NAME`, and `REGISTRY_TOKEN` in repo settings, then tag: - -```sh -git tag v0.2.1-dragonfork && git push origin v0.2.1-dragonfork -``` - --- ## v0.2.0-dragonfork (2026-05-03)