From 7112e049cbd41c316971ad487cbcc27c4bc76d43 Mon Sep 17 00:00:00 2001 From: JoshADC <71401649+JoshADC@users.noreply.github.com> Date: Sun, 8 Feb 2026 21:53:46 -0500 Subject: [PATCH] rebroadcast: fix RTMP acknowledgement uint32 overflow (#1980) The RTMP client's totalBytesReceived counter grows unbounded as a JavaScript number. When it exceeds 2^31 (~2.15 GB received), the writeUInt32BE call in sendAcknowledgementIfNeeded throws a RangeError because JavaScript bitwise operations produce signed 32-bit integers. This crashes the RTMP session and drops the video stream. For a high-bitrate camera like the Reolink D340P (~4 Mbps main stream), this overflow occurs after approximately 90 minutes of continuous streaming, causing periodic stream drops at a consistent interval. Fix by using the unsigned right shift operator (>>> 0) to keep totalBytesReceived and bytesToAck in the unsigned uint32 range [0, 4294967295], matching the RTMP spec's sequence number wrapping behavior. Co-authored-by: Josh Casada Co-authored-by: Claude Opus 4.6 --- plugins/prebuffer-mixin/src/rtmp-client.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/prebuffer-mixin/src/rtmp-client.ts b/plugins/prebuffer-mixin/src/rtmp-client.ts index 9a2210fe2..db8b88c92 100644 --- a/plugins/prebuffer-mixin/src/rtmp-client.ts +++ b/plugins/prebuffer-mixin/src/rtmp-client.ts @@ -398,7 +398,7 @@ export class RtmpClient { // Count: basic header (1 byte) + message header (0-11 bytes) + extended timestamp (0-4 bytes) + payload const extTimestampSize = (hasExtendedTimestamp || chunkStream.hasExtendedTimestamp) ? 4 : 0; const bytesInChunk = 1 + headerSize + extTimestampSize + chunkDataSize; - this.totalBytesReceived += bytesInChunk; + this.totalBytesReceived = (this.totalBytesReceived + bytesInChunk) >>> 0; // Send window acknowledgement if threshold exceeded this.sendAcknowledgementIfNeeded(); @@ -421,12 +421,14 @@ export class RtmpClient { * Send acknowledgement if window threshold exceeded */ private sendAcknowledgementIfNeeded(): void { - const bytesToAck = this.totalBytesReceived - this.lastAcknowledgementBytes; + // Handle uint32 wrap-around: if totalBytesReceived wrapped past 0, + // bytesToAck will be a large positive number, which is correct. + const bytesToAck = (this.totalBytesReceived - this.lastAcknowledgementBytes) >>> 0; if (bytesToAck >= this.windowAckSize) { this.lastAcknowledgementBytes = this.totalBytesReceived; console.log(`Sending acknowledgement: ${this.lastAcknowledgementBytes} bytes received (${bytesToAck} since last ACK)`); const data = Buffer.alloc(4); - data.writeUInt32BE(this.lastAcknowledgementBytes & 0xFFFFFFFF, 0); + data.writeUInt32BE(this.lastAcknowledgementBytes, 0); this.sendMessage(2, 0, RtmpMessageType.ACKNOWLEDGEMENT, 0, data); } }