Implements the Go RTMP stack unit-test plan (internal/rtmp), cross-referenced
to the C++ srs_utest_manual_protocol/protocol2/rtmp suites, and fixes the one
panic class the fuzz targets exposed.
Tests added (rtmp_test.go):
- TestReadMessageInterleavedMultiStream interleaved multi-cid reassembly
- TestReadMessageLargeChunkStreamID 2-/3-byte cid full-message read
- TestProtocolWritePacketReadMessageRoundTrip encoder<->decoder, 14 pkts x 4 sizes
- TestReadMessageTimestampDiscontinuity backward/forward jump, 31-bit wrap
- TestReadWriteLargePayloadChunkBoundaries chunk-boundary stress
- TestGoldenWireBytes golden bytes for headers + controls
- FuzzReadMessage / FuzzDecodeMessage / FuzzPacketUnmarshal untrusted-input fuzz
- TestPacketUnmarshalAdversarialInputs resource-safety / truncation
- TestProtocolTransactionMapConcurrency -race on the transaction map
Hardening (rtmp.go): the fuzz targets found that the variantCallPacket family
counted a stale optional-field default (CommandObject/Args pre-set to Null by a
New*Packet constructor) when the wire data was exhausted, so Size() overran the
caller's p = p[Size():] advance and panicked on truncated, untrusted input.
Reset those fields to nil before the presence check, and route the embedded
advances in CallPacket/CreateStreamResPacket/PublishPacket/PlayPacket through a
bounds-checked advanceBytes helper so any future Size()/consumed mismatch becomes
a clean error instead of a slice-out-of-range panic.
Verified: full proxy unit suite passes; TestProtocolTransactionMapConcurrency
clean under go test -race -count=3; all proxy E2E scripts pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
readBasicHeader overwrote cid with the 2-byte form (64 + byte2) before testing
whether the 3-byte form was in use, so the `cid == 1` check could never be true
and the 3-byte branch was dead code. Chunk basic headers with marker == 1 (chunk
stream IDs 320-65599) consumed only one of the two trailing bytes, leaving the
high-order byte in the stream and desyncing the chunk parser.
Keep the original marker before cid is overwritten and branch on it, matching the
C++ reference (srs_protocol_rtmp_stack.cpp, read_basic_header). The arithmetic
inside the branch was already correct.
Also correct the unit test, which had encoded the buggy result (expected cid=65
instead of 577, leaving a byte unread); it now guards the 3-byte path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port the C++ srs_protocol_rtmp_stack.cpp fix (#4356) to the Go proxy's RTMP parser in internal/rtmp.
For chunk fmt=1/2 the extended timestamp encodes a timestamp delta, not an absolute timestamp. The parser previously assigned the extended value to the message timestamp unconditionally, so once a delta reached 0xffffff the DTS was miscomputed, and since audio and video deltas differ this could cause A/V desync.
Changes:
- Split chunkStream's single ext-ts bool into hasExtendedTimestamp (presence) plus a raw extendedTimestamp uint32, mirroring the C++ has_extended_timestamp_ / extended_timestamp_ fields.
- Compute the message timestamp once: extended value when present, else the 3-byte header delta; assign it as absolute for fmt=0 and accumulate it as a delta for fmt=1/2 (and a fmt=3 first chunk).
- Resolve the 'detect the extended timestamp' TODO: peek the 4 bytes and leave them as payload when a librtmp/ffmpeg-style sender omits the ext-ts on a Type-3 chunk (Go equivalent of the C++ skip(-4)).
- Add unit tests for the fmt=1 delta-crossing-0xffffff case and the Type-3 omitted-ext-ts case.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>