diff --git a/RTMPHaishinKit/Sources/RTMP/RTMPMessage.swift b/RTMPHaishinKit/Sources/RTMP/RTMPMessage.swift index 043b94949..f224b2e79 100644 --- a/RTMPHaishinKit/Sources/RTMP/RTMPMessage.swift +++ b/RTMPHaishinKit/Sources/RTMP/RTMPMessage.swift @@ -659,8 +659,21 @@ struct RTMPUserControlMessage: RTMPMessage { init(_ header: RTMPChunkMessageHeader) { streamId = header.messageStreamId timestamp = header.timestamp - event = Event(rawValue: header.payload[1]) ?? .unknown - value = Int32(data: header.payload[2..= 6 else { + event = .unknown + value = 0 + return + } + event = Event(rawValue: payload[1]) ?? .unknown + value = Int32(data: payload[2.. empty payload, position 0 == count 0, so + // makeMessage() builds the message and runs the (previously + // unguarded) decode against an empty buffer. + let header = RTMPChunkMessageHeader( + timestmap: 0, + messageLength: 0, + messageTypeId: RTMPMessageType.user.rawValue, + messageStreamId: 0 + ) + let message = header.makeMessage() as? RTMPUserControlMessage + #expect(message?.event == .unknown) + #expect(message?.value == 0) + } + + // A well-formed 6-byte user control message must still decode correctly + // (2-byte event type + 4-byte big-endian value). + @Test func wellFormedPayloadDecodes() { + let buffer = RTMPChunkBuffer() + // basic header (fmt 0, csid 2) | timestamp(3) | length(3)=6 | + // typeId(1)=4 | streamId(4) | payload: event 0x0000 (streamBegin), + // value 0x00000001. + buffer.put(Data([2, 0, 0, 0, 0, 0, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])) + do { + let (chunkType, chunkStreamId) = try buffer.getBasicHeader() + #expect(chunkType == .zero) + #expect(chunkStreamId == 2) + let header = RTMPChunkMessageHeader() + try buffer.getMessageHeader(chunkType, messageHeader: header) + let message = header.makeMessage() as? RTMPUserControlMessage + #expect(message?.event == .streamBegin) + #expect(message?.value == 1) + } catch { + Issue.record("unexpected error decoding user control message: \(error)") + } + } +}