最开始用netmon(microsoft network monitor 3.4)分析协议,写了一个npl脚本,协议分析快完的时候才发现,原来netmon解析会有问题(似乎会丢数据),而且npl的编程性较差。后来同事说wireshark抓包不错,就换了wireshark。
wireshark(http://www.wireshark.org)是用lua做的自定义解析,编程性要好。
写了一个RTMP包的lua解析。假设packet是从握手开始抓的:
另外,解析AMF0数据时似乎会多几个0xC3字节,TCP包中有这个,但是解析成AMF结构时就将这个去掉了。在HandshakeC2这一步时,最后一个包是514bytes,解析成AMF时的tUrl的stringlength中多了一个0xC3,还有videoFunction的number值中多了一个0xC3。wireshark在解析前的TCP中也是有这两个值的,解析出来的就去掉了。是因为AMF的chunk尺寸为128,分割chunk的就是这个0xC3。(参考:http://www.acmewebworks.com/Downloads/openCS/TheAMF.pdf)。实际上是一个新的RTMP Chunk Packet,C3表示Header为1字节,ChunkStreamId为3。
lua代码:
-- for rtmp specs 1.0 -- winlin, 2012.5.16 -- for Version 1.6.0 (SVN Rev 37592 from /trunk-1.6) -- about winlin if (gui_enabled()) then -- about function about_winlin() browser_open_url("http://blog.csdn.net/winlinvip"); end register_menu("Lua/Winlin", about_winlin, MENU_TOOLS_UNSORTED); end -- parse my rtmp do --[[ ============================================================================== ========================global, consts and protocol=========================== ============================================================================== ]] -- define the rtmp protocol local rtmp = Proto("AdobeRtmp", "Adobe RTMP(Real Time Messaging Protocol)"); -- the logs displayed on the LuaConsole. debug(string.format("[global] protocol rtmp initialized.")); -- consts and pool definition. -- const defines the server port. local rtmpServerPort = 1935; --[[ defines a global objects table, to stores all packets. table(key=pkt.number, value=rtmpObject). rtmpObject defines as: { id: int, the pkt.number. src_port: int, the pkt.src_port. type: int, the pakcet type: -1.unknown, 1.C0+C1, 2.S0+S1+S2, 3.C2, 4.ChunkPakcet. show: bool, whether show in ui, false if analysis, otherwise true. pre: int, previous pkt.number if it's a fragment of tcp packet. -1, uninitialized; 0, no pre; positive, the previous packet id. refpos: int, the referenced position of previous packet. if 0, refer whole object(must refer to the pre of pre); if positive, refer to it only. next: int, next pkt.number if it's a fragment of tcp packet. -1, uninitialized; 0, no next; positive, the next packet id. bytes: ByteArray, the packet bytes. packet_names: a list that contains a list of parsed names. e.g "C0+C1", "connect", "#error" } ]] local rtmpObjectPool = {}; -- packet decoded number, sometimes seems the wireshark will parse the packets again! -- for example, we parsed the #4,#5,#8 packets, then the next is #4,#5,#8 again! local rtmpTcpPacketNumber = 0; -- defines the next packets type: 1.C0+C1, 2.S0+S1+S2, 3.C2, 4.ChunkPakcet. -- TODO: must dynamically determinate the packet type. local rtmpNextPacketType = 1; -- record the parsed packet count. local rtmpParsedPacketCount = 0; -- record the parsed valid message length local rtmpParsedMessageLength = 0; -- define a global bytes pool, to reassemble the RTMP pakcets --[[ there is a bug for ByteArray, see https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=4461 TestCase: ba = ByteArray.new("02") ba:append(ByteArray.new("03")) wireshark will crash! This bug causes Wireshark 1.6.1 to crash on linux and Windows. A workaround is to use ByteArray.new(). For example, to prepend ByteArray B to ByteArray A: A = ByteArray.new("02") B = ByteArray.new("03") local C = ByteArray.new( tostring(B)..tostring(A) ) ]] local rtmpBytesPool = ByteArray.new(); debug(string.format("[global] the rtmp initialized. bytes pool size=%d. server port=%d", rtmpBytesPool:len(), rtmpServerPort)); -- defines the protocol structures. -- rtmp packet, it actually must be a rtmpHandShake or/and rtmpChunk packet. local rtmpPacket = { -- the handshake structure used during handshake. rtmpHandShake = {}, -- the chunk structure is the actual packet after handshaked. rtmpChunk = { -- the chunk header. ChunkHeader = { -- basic header BasicHeader = { ChunkType = {}, StreamIdLow = {}, StreamIdHigh = {} }, -- chunk message header. ChunkMsgHeader = { TimestampDelta = {}, MessageLength = {}, MessageTypeId = {}, MessageStreamId = {} }, -- extended timestamp. ExtendedTimeStamp = {} }, -- the chunk data. ChunkData = {} } }; -- alias local rtmpChunkHeader = rtmpPacket.rtmpChunk.ChunkHeader; local rtmpBasicHeader = rtmpPacket.rtmpChunk.ChunkHeader.BasicHeader; local rtmpChunkType = rtmpPacket.rtmpChunk.ChunkHeader.BasicHeader.ChunkType; local rtmpStreamIdLow = rtmpPacket.rtmpChunk.ChunkHeader.BasicHeader.StreamIdLow; local rtmpStreamIdHigh = rtmpPacket.rtmpChunk.ChunkHeader.BasicHeader.StreamIdHigh; local rtmpTimestampDelta = rtmpPacket.rtmpChunk.ChunkHeader.ChunkMsgHeader.TimestampDelta; local rtmpMessageLength = rtmpPacket.rtmpChunk.ChunkHeader.ChunkMsgHeader.MessageLength; local rtmpMessageTypeId = rtmpPacket.rtmpChunk.ChunkHeader.ChunkMsgHeader.MessageTypeId; local rtmpMessageStreamId = rtmpPacket.rtmpChunk.ChunkHeader.ChunkMsgHeader.MessageStreamId; local rtmpExtendedTimeStamp = rtmpPacket.rtmpChunk.ChunkHeader.ExtendedTimeStamp; local rtmpChunkData= rtmpPacket.rtmpChunk.ChunkHeader.ChunkData; -- defines the frame based variables local rtmpCurrentPacket = {}; -- the current pakcet, set in the dissector, specifies the current packet. local rtmpCurrentBuffer = {}; -- the current buffer(TvbRange object) of current packet. local rtmpCurrentTree = {}; -- the display tree root of current pakcet. local rtmpCurrentObject = nil; -- the current object mapped to current packet. --[[: the parse status of current packet. ==0 success parsed. -1 bytes in buffer is not enouth, need more data. -2 parse error, must clear all data in buffer. -3 parse the chunk error, next packet must partially reference to this. see also: refpos(refence position). ]] local rtmpCurrentStatus = 0; debug(string.format("[global] global protocol variables and alias defined")); -- define the decoder. --[[ the dissctor will be invoked many times. if we has 3 packets to dissect: 1. on wireshark start: we get 6packets to dissect, in sequence of #1,#2,#3,#1,#2,#3. for the packet list will parse the packets for #1#2#3 then the packet detail will parse the packets again for #1#2#3. 2. if user click packet #2: we get #2 to dissect. so if #1 and #2 is one packet(separate in two tcp packet), we cannnot parse right if only click #2, but ok to click #1 then #2. ]] function rtmp.dissector(buf, pkt, tree) debug(string.format("[disscet] ==============================================================================================")); debug(string.format("[dissect] get a pakcet to dissect: parsed=%d id=%d, len=%d, old pool=%d, sender=%s", rtmpParsedPacketCount, pkt.number, buf:len(), rtmpBytesPool:len(), utilityRtmpDiscoverySender(pkt))); local rtmpObject = utilityRtmpCreateOrGetObject(pkt, buf); local buffer = utilityRtmpAppendBytesPool(rtmpObject); -- invoke the handler when buf changed. onRtmpBeginDissect(rtmpObject, buffer, pkt, tree); -- set the columns info. uiRtmpColumnProtocolSet(string.format("RTMP")); -- the first time, must set the column text, then the append is available. uiRtmpColumnInfoSet(string.format("RTMP: ", buf:len(), buffer:len())); -- definese the parse offset. local offset = 0; local length = buffer:len(); -- dessector the packets local root = uiRtmpTreeCreate(tree, buffer(0, buffer:len()), buffer:len()); uiRtmpTreeSetText(root, string.format("AdobeRTMP: linkups=[%s]", utilityRtmpGetObjectLinkupString(rtmpObject))); offset = rtmpDissectAllPackets(root, buffer, offset); if(rtmpCurrentStatus == 0) then debug(string.format("[dissect] dissect packets success. consumed size:%d, new pool size:%d, bytes left:%d", offset, length, length - offset)); onRtmpDiessectSuccess(); elseif(rtmpCurrentStatus == -1) then debug(string.format("[dissect] dissect some packet finished, need more bytes. consumed size:%d, pool size:%d, bytes left:%d", offset, length, length - offset)); onRtmpDiessectError(length, offset); elseif(rtmpCurrentStatus == -2)then critical(string.format("[dissect] dissect packet error, simply consumed all bytes in buffer!")); onRtmpDiessectError(length, offset); elseif(rtmpCurrentStatus == -3)then critical(string.format("[dissect] dissect the chunk data error, this packet %d partially provides %d bytes for next.", rtmpCurrentObject.id, length - offset)); onRtmpDiessectError(length, offset); else critical(string.format("[dissect] unknown status=%d", rtmpCurrentStatus)); end onRtmpDissectFinished(root, length, offset); -- invoke the handler. onRtmpEndDissect(); end --[[ ============================================================================== ========================defines the event handlers============================ ============================================================================== ]] --[[ when the diessector start to dissect packet, initialize the frame level variables. ]] function onRtmpBeginDissect(rtmpObject, buf, pkt, tree) rtmpCurrentPacket = pkt; rtmpCurrentBuffer = buf; rtmpCurrentTree = tree; rtmpCurrentStatus = 0; rtmpParsedPacketCount = rtmpParsedPacketCount + 1; rtmpCurrentObject = rtmpObject; -- clear the parsed packet names during every paring. rtmpCurrentObject.packet_names = {}; end --[[ when the diessector finish to dissect packet ]] function onRtmpDissectFinished(root, length, offset) -- add parsed packet names to protocol info column local i = table.maxn(rtmpCurrentObject.packet_names) + 1; while(i >= 0)do local item = rtmpCurrentObject.packet_names[i]; if(item ~= nil)then uiRtmpColumnInfoAppend(item); if(i > 0)then uiRtmpColumnInfoAppend(" | "); end end i = i - 1; end -- update final status local status = "Success"; if(rtmpCurrentStatus == -1)then status = "#HasContinuePackets"; elseif(rtmpCurrentStatus == -2)then status = "#ParseError"; end uiRtmpTreeAppendText(root, string.format(" pool=%d consumed=%d available=%d status=%s", length, offset, length - offset, status)); end --[[ when the diessector exit to dissect packet ]] function onRtmpEndDissect() rtmpCurrentPacket = nil; rtmpCurrentBuffer = nil; rtmpCurrentTree = nil; rtmpCurrentStatus = 0; rtmpCurrentObject = nil; end --[[ when the diessector dissect pakcet success. ]] function onRtmpDiessectSuccess() rtmpCurrentObject.show = true; rtmpCurrentObject.next = 0; end --[[ when the diessector dissect pakcet error. ]] function onRtmpDiessectError(length, offset) -- if parsed error( for the AMF parsed error, must show it ). if(rtmpCurrentStatus == -2)then onRtmpDiessectSuccess(); -- if need more, show it. elseif(rtmpCurrentStatus == -1)then utilityRtmpAddParsedPacketName("#Partial"); rtmpCurrentObject.show = true; rtmpCurrentObject.next = -1; -- has a partial continue packet elseif(rtmpCurrentStatus == -3)then rtmpCurrentObject.show = true; rtmpCurrentObject.next = -1; -- the partial bytes for the next packet. local availableBytes = length - offset; local offsetForNextPacket = rtmpCurrentObject.bytes:len() - availableBytes; rtmpCurrentObject.refpos = offsetForNextPacket; end end --[[ ============================================================================== ========================defines the parse methods============================= ============================================================================== ]] --[[ dissector the packets from buffer and append items to root. return the consumed bytes and set the global status rtmpCurrentStatus: ]] function rtmpDissectAllPackets(root, buffer, offset) rtmpCurrentStatus = 0; local continueLoop = true; local consumedSize = 0; local availableSize = buffer:len() - offset; debug(string.format("[dissect-all] start to dissect all packets. offset=%d, pool=%d, available=%d", offset, buffer:len(), availableSize)); while(continueLoop) do local forcePacketType = -1; -- if C2(type3) parsed success, force to parse ChunkPacket(type4) if(rtmpCurrentObject.type == 3 and consumedSize > 0)then forcePacketType = 4; end local size = rtmpDissectSinglePacket(root, buffer, offset + consumedSize, forcePacketType); consumedSize = consumedSize + size; availableSize = availableSize - size; -- if error or consumed nothing, dissect packets in the next roudntrip. continueLoop = (rtmpCurrentStatus == 0 and size > 0 and availableSize > 0); end debug(string.format("[dissect-all] disssect finished. offset=%d, pool=%d, consumed bytes size=%d", offset, buffer:len(), consumedSize)); return consumedSize; end --[[ dissector a single packet from buffer and append items to root. return the consumed bytes and set the global status rtmpCurrentStatus. @param forcePacketType: int value indicates the current packet type. -1, ignore this type, the function will discovery the right type. such as 1,2,3,4. positive, use this type. for example, a packet contains C2|ChunkPacket, and its packet type is 3(C2), so first time, we pass -1, to parse the C2 packet. second time, we pass 4, to force to parse the bytes in type4(ChunkPacket). ]] function rtmpDissectSinglePacket(root, buffer, offset, forcePacketType) -- if object contains type, use it. -- otherwise, use the type in parse sequence. local packetType = rtmpCurrentObject.type; if(packetType == -1)then packetType = rtmpNextPacketType; end if(forcePacketType > 0)then packetType = forcePacketType; end debug(string.format( "[dissect-one] start dissect a single packet. %s, pool size:%d, offset:%d, available=%d", string.format("actual type:%d (logical:%d, object:%d, force:%d)", packetType, rtmpNextPacketType, rtmpCurrentObject.type, forcePacketType), buffer:len(), offset, buffer:len() - offset )); local size = 0; -- 1.C0+C1 if(packetType == 1)then size = rtmpDissectHandShakeC0C1(root, buffer, offset); -- 2.S0+S1+S2 elseif(packetType == 2)then size = rtmpDissectHandShakeS0S1S2(root, buffer, offset); -- 3.C2 elseif(packetType == 3)then size = rtmpDissectHandShakeC2(root, buffer, offset); -- 4.ChunkPakcet. elseif(packetType == 4)then size = rtmpDissectChunkPacket(root, buffer, offset); -- invalid packet. else rtmpCurrentStatus = -2; uiRtmpTreeCreate(root, buffer(offset, buffer:len()), "#Invalid Packet Type"); critical(string.format("[dissect-one] invalid packet type %d, must be 1/2/3/4.", packetType)); end debug(string.format("[dissect-one] finished to dissect a single packet. consumed:%d", size)); return size; end --[[ dissector the c0c1 handshake packets from buffer and append items to root. return the consumed bytes and set the global status rtmpCurrentStatus: ]] function rtmpDissectHandShakeC0C1(root, buffer, offset) local C0C1length = 1 + 1536; local C1length = 1536; debug(string.format("[dissect-c0c1] start to dissect the client handshake pakcet: C0C1. required %d bytes, pool size=%d", C0C1length, buffer:len())); rtmpCurrentObject.type = 1; if(buffer:len() < C0C1length)then uiRtmpTreeCreate(root, buffer(offset, buffer:len() - offset), "ClientHandshake(C0C1) #Not Enough"); warn(string.format("[dissect-c0c1] failed to dissect the client handshake packet C0C1. required %d bytes, pool size=%d", C0C1length, buffer:len())); rtmpCurrentStatus = -1; return 0; else local size = 0; local c0c1 = uiRtmpTreeCreate(root, buffer(offset, C0C1length), "ClientHandshake: C0C1"); -- C0: Version: 8 bits local version = buffer(offset + size, 1):uint(); debug(string.format("[dissect-c0c1] c0 version decoded: version=%d", version)); -- the C0C1 must start with 0x03. if(version ~= 0x03)then uiRtmpTreeSetText(c0c1, "ClientHandshake: #Corrupt Pakcet"); critical(string.format("[dissect-c0c1] corrupt dissct, the C0C1 packet must startwith 0x03. actual:%d(0x%X)", version, version)); rtmpCurrentStatus = -2; return 0; end uiRtmpTreeCreate(c0c1, buffer(offset + size, 1), string.format("C0(Version): %d(0x%X)", version, version)); size = size + 1; -- C1: The C1 and S1 packets are 1536 octets long uiRtmpTreeCreate(c0c1, buffer(offset + size, C1length), string.format("C1(Signature): size=%d(0x%X)", C1length, C1length)); size = size + C1length; -- auto change. utilityRtmpAddParsedPacketName("HandShake C0+C1"); rtmpNextPacketType = 2; debug(string.format("[dissect-c0c1] C0C1 packet decoded. consumed size:%d, packet type change to:%d, object type:%d", size, rtmpNextPacketType, rtmpCurrentObject.type)); return size; end return 0; end --[[ dissector the S0S1S2 handshake packets from buffer and append items to root. return the consumed bytes and set the global status rtmpCurrentStatus: ]] function rtmpDissectHandShakeS0S1S2(root, buffer, offset) local S0S1S2length = 1 + 1536 + 1536; local S1OrS2length = 1536; debug(string.format("[dissect-s0s1s2] start to dissect the server handshake pakcet: S0S1S2. required %d bytes, pool size=%d", S0S1S2length, buffer:len())); rtmpCurrentObject.type = 2; if(buffer:len() < S0S1S2length)then uiRtmpTreeCreate(root, buffer(offset, buffer:len() - offset), "ServerHandshake(S0S1S2) #Not Enough"); warn(string.format("[dissect-s0s1s2] failed to dissect the server handshake packet s0s1s2. required %d bytes, pool size=%d", S0S1S2length, buffer:len())); rtmpCurrentStatus = -1; return 0; else local size = 0; local s0s1s2 = uiRtmpTreeCreate(root, buffer(offset, S0S1S2length), "ServerHandshake: S0S1S2"); -- S0: Version: 8 bits local version = buffer(offset + size, 1):uint(); debug(string.format("[dissect-s0s1s2] S0 version decoded: version=%d", version)); -- the S0S1S2 must start with 0x03. if(version ~= 0x03)then uiRtmpTreeSetText(s0s1s2, "ServerHandshake: #Corrupt Pakcet"); critical(string.format("[dissect-s0s1s2] corrupt dissct, the S0S1S2 packet must startwith 0x03. actual:%d(0x%X)", version, version)); rtmpCurrentStatus = -2; return 0; end uiRtmpTreeCreate(s0s1s2, buffer(offset + size, 1), string.format("S0(Version): %d(0x%X)", version, version)); size = size + 1; -- S1: The C1 and S1 packets are 1536 octets long uiRtmpTreeCreate(s0s1s2, buffer(offset + size, S1OrS2length), string.format("C1(Signature): size=%d(0x%X)", S1OrS2length, S1OrS2length)); size = size + S1OrS2length; -- S2: The C2 and S2 packets are 1536 octets long uiRtmpTreeCreate(s0s1s2, buffer(offset + size, S1OrS2length), string.format("C1(Signature): size=%d(0x%X)", S1OrS2length, S1OrS2length)); size = size + S1OrS2length; -- auto change. utilityRtmpAddParsedPacketName("HandShake S0+S1+S2"); rtmpNextPacketType = 3; debug(string.format("[dissect-s0s1s2] S0S1S2 packet decoded. consumed size:%d, packet type change to:%d, object type:%d", size, rtmpNextPacketType, rtmpCurrentObject.type)); return size; end return 0; end --[[ dissector the C2 handshake packets from buffer and append items to root. return the consumed bytes and set the global status rtmpCurrentStatus: ]] function rtmpDissectHandShakeC2(root, buffer, offset) local C2length = 1536; debug(string.format("[dissect-c2] start to dissect the client handshake pakcet: C2. required %d bytes, pool size=%d", C2length, buffer:len())); rtmpCurrentObject.type = 3; if(buffer:len() < C2length)then uiRtmpTreeCreate(root, buffer(offset, buffer:len() - offset), "ClientHandshake(C2) #Not Enough"); warn(string.format("[dissect-c2] failed to dissect the server handshake packet c2. required %d bytes, pool size=%d", C2length, buffer:len())); rtmpCurrentStatus = -1; return 0; else local size = 0; local c2 = uiRtmpTreeCreate(root, buffer(offset, C2length), "ClientHandshake: C2"); -- C2: The C2 and S2 packets are 1536 octets long uiRtmpTreeCreate(c2, buffer(offset + size, C2length), string.format("C2(Signature): size=%d(0x%X)", C2length, C2length)); size = size + C2length; -- auto change. utilityRtmpAddParsedPacketName("HandShake C2"); rtmpNextPacketType = 4; debug(string.format("[dissect-c2] C2 packet decoded. consumed size:%d, packet type change to:%d, object type:%d", size, rtmpNextPacketType, rtmpCurrentObject.type)); return size; end return 0; end --[[ dissector the chunk pakcet(common packet) from buffer and append items to tree. return the consumed bytes and set the global status rtmpCurrentStatus: ]] function rtmpDissectChunkPacket(tree, buffer, offset) local size = 0; debug(string.format("[dissect-chunk] start to dissect a chunk stream. pool size:%d, offset:%d, available:%d", buffer:len(), offset, buffer:len() - offset)); local root = uiRtmpTreeCreate(tree, buffer(offset, buffer:len() - offset), nil); -- parse chunk header size = size + rtmpParseChunkHeader(root, buffer, offset + size); -- if parse chunk header success, parse chunk data. if(rtmpCurrentStatus == 0)then -- parse chunk data size = size + rtmpParseChunkData(root, buffer, offset + size); -- if packet parsed success, update desc if(rtmpCurrentStatus == 0)then warn(string.format("[dissect-chunk] parse chunk packet success. pool size=%d, offset=%d, consumed=%d, available=%d", buffer:len(), offset, size, buffer:len() - offset - size)); uiRtmpTreeSetText(root, string.format("RTMP: size=%d(0x%X)", size, size)); -- only after the packet is dissected success, set the name and size. utilityRtmpAddParsedPacketName(utilityRtmpMessageTypeIdDesc(rtmpMessageTypeId)); uiRtmpTreeSetLength(root, size); else warn(string.format("[dissect-chunk] parse chunk data error. consumed size=%d(will reset to 0), status=%d", size, rtmpCurrentStatus)); size = 0; uiRtmpTreeSetText(root, string.format("RTMP: #Partial")); end -- if not success, only consume 0bytes, the high-level function knows how to process it. else -- force to drop the corrupted datas. -- the header must not parsed failed: maybe some corrupt datas! I donot know why. -- TODO: fix the corrupt data. -- there are more 2bytes at the end of packet, it is from: -- the TCP data is 514bytes, which contains(AMF0objects) such sequence(0x0120): 74 63 55 72 6C 02 C3 00 1F 72 74 6D 70 3A -- the 02 is string type, C3 00 is string length, decoded as: (type)string (length)193 (value).rtmp: -- but in the parsed packets the C3 is removed! the parsed packet sequence is(0x0120): 74 63 55 72 6C 02 00 1F 72 74 6D 70 3A -- and decoded is: (type)string (length)31 (value)rtmp: -- so, must ensure the amf decode success. rtmpCurrentStatus = -2; utilityRtmpAddParsedPacketName("#Error"); warn(string.format("[dissect-chunk] parse chunk header error. consumed size=%d(will reset to 0), status=%d", size, rtmpCurrentStatus)); size = 0; uiRtmpTreeSetText(root, string.format("RTMP: #Parse ChunkHeader Error")); end if(rtmpCurrentObject.type == -1)then rtmpCurrentObject.type = 4; end debug(string.format("[dissect-chunk] chunk dissect finihsed. next type is:%d, object type:%d", rtmpNextPacketType, rtmpCurrentObject.type)); return size; end --[[ dissector the chunk header from buffer and append items to tree. return the consumed bytes and set the global status rtmpCurrentStatus: ]] --[[ Chunk Format Each chunk consists of a header and data. The header itself is broken down into three parts: +-------------+----------------+-------------------+--------------+ | Basic header|Chunk Msg Header|Extended Time Stamp| Chunk Data | +-------------+----------------+-------------------+--------------+ Figure 5 Chunk Format. ]] function rtmpParseChunkHeader(root, buf, offset) local size = 0; local ChunkHeader = uiRtmpTreeCreate(root, buf(offset + size, 0), string.format("ChunkHeader")); -- parse basic header size = size + rtmpParseBasicHeader(ChunkHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end -- parse chunk message header size = size + rtmpParseChunkMsgHeader(ChunkHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end -- parse extended timestamp size = size + rtmpParseExtendedTimeStamp(ChunkHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end uiRtmpTreeSetLength(ChunkHeader, size); uiRtmpTreeSetText(ChunkHeader, string.format("ChunkHeader: size=%d(0x%X)", size, size)); return size; end --[[ Chunk basic header: 1 to 3 bytes This field encodes the chunk stream ID and the chunk type. Chunk type determines the format of the encoded message header. The length depends entirely on the chunk stream ID, which is a variable-length field. ]] function rtmpParseBasicHeader(ChunkHeader, buf, offset) local size = 0; local BasicHeader = uiRtmpTreeCreate(ChunkHeader, buf(offset + size, size), string.format("BasicHeader")); size = size + rtmpParseChunkType(BasicHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end size = size + rtmpParseStreamIdLow(BasicHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end size = size + rtmpParseStreamIdHigh(BasicHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end uiRtmpTreeSetLength(BasicHeader, size); uiRtmpTreeSetText(BasicHeader, string.format("BasicHeader: size=%d(0x%X)", size, size)); return size; end --[[ the chunk type is represented by fmt field fmt: 2 bits This field identifies one of four format used by the ‘chunk message header’.The ‘chunk message header’ for each of the chunk types is explained in the next section. ]] function rtmpParseChunkType(BasicHeader, buf, offset) -- check the buffer overflow. if(offset + 1 > buf:len())then warn("[dissect-chunk] need more bytes by chunk type. buf:%d offset:%d reqired:%d", buf:len(), offset, 1); rtmpCurrentStatus = -1; return 0; end -- UINT8 ChunkType:2 // 2bits rtmpChunkType = buf(offset, 1):bitfield(0, 2); uiRtmpTreeCreate(BasicHeader, buf(offset, 1), string.format("ChunkType: %d (0x%x) %s", rtmpChunkType, rtmpChunkType, utilityRtmpChunkTypeDesc(rtmpChunkType))); return 0; end --[[ [0, 63] The IDs 0, 1, and 2 are reserved. 0: StreamIdHigh is 1bytes. 1: StreamIdHigh is 2bytes. 2-63: StreamIdHigh is 0bytes. Chunk stream IDs 2-63 can be encoded in the 1-byte version of this field. 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |fmt| cs id | +-+-+-+-+-+-+-+-+ Figure 6 Chunk basic header 1 ]] function rtmpParseStreamIdLow(BasicHeader, buf, offset) -- check the buffer overflow. if(offset + 1 > buf:len())then warn("[dissect-chunk] need more bytes by chunk stream id low. buf:%d offset:%d reqired:%d", buf:len(), offset, 1); rtmpCurrentStatus = -1; return 0; end -- UINT8 StreamIdLow:6 // 6bits rtmpStreamIdLow = buf(offset, 1):bitfield(2, 6); uiRtmpTreeCreate(BasicHeader, buf(offset, 1), string.format("StreamIdLow: %d (0x%x) %s", rtmpStreamIdLow, rtmpStreamIdLow, utilityRtmpStreamIdLowDescrition(rtmpStreamIdLow))); return 1; end --[[ winlin: if StreamIdLow is not enough, use the high. ]] function rtmpParseStreamIdHigh(BasicHeader, buf, offset) local size = 0; -- UINT8 StreamIdHigh: 0, 1, 2 bytes if(rtmpStreamIdLow == 0) then --[[ Value 0 indicates the ID in the range of 64–319 (the second byte + 64). Chunk stream IDs 64-319 can be encoded in the 2-byte version of this field. ID is computed as (the second byte + 64). 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |fmt| 0 | cs id - 64 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Figure 7 Chunk basic header 2 This field contains the chunk stream ID minus 64. ]] -- UINT8 StreamIdHigh // 1bytes size = 1; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by chunk stream id high. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpStreamIdHigh = buf(offset, size):uint(); uiRtmpTreeCreate(BasicHeader, buf(offset, size), string.format("StreamIdHigh: %d (%x)", rtmpStreamIdHigh, rtmpStreamIdHigh)); elseif(rtmpStreamIdLow == 1) then --[[ Value 1 indicates the ID in the range of 64–65599 ((the third byte)*256 + the second byte + 64). Chunk stream IDs 64-65599 can be encoded in the 3-byte version of this field. ID is computed as ((the third byte)*256 + the second byte + 64). 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |fmt| 1 | cs id - 64 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Figure 8 Chunk basic header 3 This field contains the chunk stream ID minus 64. ]] -- UINT16 StreamIdHigh // 2bytes size = 2; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by chunk stream id high. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpStreamIdHigh = buf(offset, size):uint(); uiRtmpTreeCreate(BasicHeader, buf(offset, size), string.format("StreamIdHigh: %d (%x)", rtmpStreamIdHigh, rtmpStreamIdHigh)); else -- RtmpNone StreamIdHigh; // 0bytes rtmpStreamIdHigh = -1; uiRtmpTreeCreate(BasicHeader, buf(offset, size), string.format("StreamIdHigh: None")); end return size; end --[[ There are four different formats for the chunk message header, selected by the "fmt" field in the chunk basic header. ]] function rtmpParseChunkMsgHeader(ChunkHeader, buf, offset) local size = 0; local ChunkMsgHeader = uiRtmpTreeCreate(ChunkHeader, buf(offset + size, size), string.format("ChunkMsgHeader")); size = size + rtmpParseTimestampDelta(ChunkMsgHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end size = size + rtmpParseMessageLength(ChunkMsgHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end size = size + rtmpParseMessageTypeId(ChunkMsgHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end size = size + rtmpParseMessageStreamId(ChunkMsgHeader, buf, offset + size); if(rtmpCurrentStatus ~= 0)then return 0; end uiRtmpTreeSetLength(ChunkMsgHeader, size); uiRtmpTreeSetText(ChunkMsgHeader, string.format("ChunkMsgHeader: size=%d(0x%X)", size, size)); return size; end --[[ timestamp delta: 3 bytes For a type-1 or type-2 chunk, the difference between the previous chunk's timestamp and the current chunk's timestamp is sent here. If the delta is greater than or equal to 16777215 (hexadecimal 0x00ffffff), this value MUST be 16777215, and the ‘extended timestamp header’ MUST be present. Otherwise, this value SHOULD be the entire delta. ]] function rtmpParseTimestampDelta(ChunkMsgHeader, buf, offset) local size = 0; if(rtmpChunkType == 0 or rtmpChunkType == 1 or rtmpChunkType == 2) then -- UINT24 TimestampDelta; // 3bytes size = 3; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by timestamp delta. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpTimestampDelta = buf(offset, size):uint(); uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("TimestampDelta: %d (0x%x)", rtmpTimestampDelta, rtmpTimestampDelta)); else -- RtmpNone TimestampDelta; // 0bytes rtmpTimestampDelta = -1; uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("TimestampDelta: None")); end return size; end --[[ message length: 3 bytes For a type-0 or type-1 chunk, the length of the message is sent here. Note that this is generally not the same as the length of the chunk payload. The chunk payload length is the maximum chunk size for all but the last chunk, and the remainder (which may be the entire length, for small messages) for the last chunk. ]] function rtmpParseMessageLength(ChunkMsgHeader, buf, offset) local size = 0; if(rtmpChunkType == 0 or rtmpChunkType == 1) then -- UINT24 MessageLength; // 3bytes size = 3; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by message length. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpMessageLength = buf(offset, size):uint(); debug(string.format("[dissect-chunk] the message length parsed. len=%d, previous=%d", rtmpMessageLength, rtmpParsedMessageLength)); rtmpParsedMessageLength = rtmpMessageLength; uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageLength: %d (0x%x)", rtmpMessageLength, rtmpMessageLength)); else -- RtmpNone MessageLength; // 0bytes rtmpMessageLength = -1; warn(string.format("[dissect-chunk] the message length parse failed, use previous=%d", rtmpParsedMessageLength)); uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageLength: None")); end return size; end --[[ message type id: 1 byte For a type-0 or type-1 chunk, type of the message is sent here. ]] function rtmpParseMessageTypeId(ChunkMsgHeader, buf, offset) local size = 0; if(rtmpChunkType == 0 or rtmpChunkType == 1) then -- UINT8 MessageTypeId; // 1bytes size = 1; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by message type id. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpMessageTypeId = buf(offset, size):uint(); uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageTypeId: %d (0x%x) %s", rtmpMessageTypeId, rtmpMessageTypeId, utilityRtmpMessageTypeIdDesc(rtmpMessageTypeId))); else -- RtmpNone MessageTypeId; // 0bytes rtmpMessageTypeId = -1; uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageTypeId: None")); end return size; end --[[ message stream id: 4 bytes For a type-0 chunk, the message stream ID is stored. Message stream ID is stored in little-endian format. Typically, all messages in the same chunk stream will come from the same message stream. While it is possible to multiplex separate message streams into the same chunk stream, this defeats all of the header compression. However, if one message stream is closed and another one subsequently opened, there is no reason an existing chunk stream cannot be reused by sending a new type-0 chunk. ]] function rtmpParseMessageStreamId(ChunkMsgHeader, buf, offset) local size = 0; if(rtmpChunkType == 0) then -- UINT32 MessageStreamId; // 4bytes size = 4; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by message stream id. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end -- WINLIN: in little-endian format. rtmpMessageStreamId = buf(offset, size):le_uint(); uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageStreamId: %d (0x%x)", rtmpMessageStreamId, rtmpMessageStreamId)); else -- RtmpNone MessageStreamId; // 0bytes rtmpMessageStreamId = -1; uiRtmpTreeCreate(ChunkMsgHeader, buf(offset, size), string.format("MessageStreamId: None")); end return size; end --[[ ExtendedTimeStamp This field is transmitted only when the normal time stamp in the chunk message header is set to 0x00ffffff. If normal time stamp is set to any value less than 0x00ffffff, this field MUST NOT be present. This field MUST NOT be present if the timestamp field is not present. Type 3 chunks MUST NOT have this field. 0 or 4 bytes ]] function rtmpParseExtendedTimeStamp(ChunkHeader, buf, offset) local size = 0; if(rtmpTimestampDelta == 0x00ffffff) then -- UINT32 ExtendedTimeStamp; // 4bytes size = 4; -- check the buffer overflow. if(offset + size > buf:len())then warn("[dissect-chunk] need more bytes by extended timestamp. buf:%d offset:%d reqired:%d", buf:len(), offset, size); rtmpCurrentStatus = -1; return 0; end rtmpExtendedTimeStamp = buf(offset, size):uint(); uiRtmpTreeCreate(ChunkHeader, buf(offset, size), string.format("ExtendedTimeStamp: %d(0x%X) (size=%d)", rtmpExtendedTimeStamp, rtmpExtendedTimeStamp, size)); else -- RtmpNone ExtendedTimeStamp; // 0bytes rtmpExtendedTimeStamp = -1; uiRtmpTreeCreate(ChunkHeader, buf(offset, 0), string.format("ExtendedTimeStamp: None (size=%d)", size)); end return size; end --[[ ]] function rtmpParseChunkData(root, buf, offset) local size = 0; -- if require more message, consumed 0. if(buf:len() < offset + rtmpParsedMessageLength)then warn(string.format("[dissect-chunk] the chunk data need more bytes to parse. pool size=%d, offset=%d, available=%d, required=%d", buf:len(), offset, buf:len() - offset, rtmpParsedMessageLength)); -- we think if chunk data cannot parse, it's error: a TCP packet must contains a whole chunk. rtmpCurrentStatus = -3; utilityRtmpAddParsedPacketName("#Partial"); return size; end -- consume the message data size = rtmpParsedMessageLength; local ChunkData = uiRtmpTreeCreate(root, buf(offset, size), string.format("ChunkData: size=%d(0x%x)", size, size)); debug(string.format("[dissect-chunk] the chunk data dissect success. pool size=%d, consumed size=%d", buf:len(), size)); return size; end --[[ ============================================================================== ========================defines the ui methods================================ ============================================================================== ]] -- set the PacketList protocol column text function uiRtmpColumnProtocolSet(msg) if(rtmpCurrentObject.show)then rtmpCurrentPacket.cols.protocol:set(msg); end end -- set the PacketList info column text function uiRtmpColumnInfoSet(msg) if(rtmpCurrentObject.show)then rtmpCurrentPacket.cols.info:set(msg); end end -- append the PacketList info column text function uiRtmpColumnInfoAppend(msg) if(rtmpCurrentObject.show)then rtmpCurrentPacket.cols.info:append(msg); end end -- set the PacketDetail tree item size. function uiRtmpTreeSetLength(root, size) if(rtmpCurrentObject.show and root ~= nil)then root:set_len(size); end end -- set the PacketDetail tree item text. function uiRtmpTreeSetText(root, msg) if(rtmpCurrentObject.show and root ~= nil)then root:set_text(msg); end end -- append the PacketDetail tree item text. function uiRtmpTreeAppendText(root, msg) if(rtmpCurrentObject.show and root ~= nil)then root:append_text(msg); end end -- create the PacketDetail sub tree item. function uiRtmpTreeCreate(tree, buf, msg) if(rtmpCurrentObject.show and tree ~= nil)then if(buf ~= nil)then if(msg ~= nil)then return tree:add(rtmp, buf, msg); else return tree:add(rtmp, buf); end else return tree:add(rtmp); end else return nil; end end --[[ ============================================================================== ========================defines the utility methods=========================== ============================================================================== ]] --[[ value: rtmpChunkType (BasicHeader.ChunkType) ]] function utilityRtmpChunkTypeDesc(rtmpChunkType) local value = rtmpChunkType; if(value == 0x00) then return "NewChunkAndMessage: start of a new chunk stream for new message."; elseif(value == 0x01) then return "SameMessageStream: same stream ID, variable-sized message streams."; elseif(value == 0x02) then return "RegisterTimeDelta: only timestamp delta, constant-sized message streams."; elseif(value == 0x03) then return "SameAsPreviousChunk: no header, take values from the preceding chunk."; else return "UnknownChunkType"; end end --[[ value: rtmpStreamIdLow (BasicHeader.StreamIdLow) ]] function utilityRtmpStreamIdLowDescrition(rtmpStreamIdLow) local value = rtmpStreamIdLow; if(value == 0x02) then return "(low-level message)"; -- 2: low-level message; otherwise, normal message. else return ""; end end --[[ value: rtmpMessageTypeId (ChunkMsgHeader.MessageTypeId) ]] function utilityRtmpMessageTypeIdDesc(rtmpMessageTypeId) local value = rtmpMessageTypeId; --[[ 5. Protocol Control Messages RTMP reserves message type IDs 1-7 for protocol control messages. These messages contain information needed by the RTM Chunk Stream protocol or RTMP itself. Protocol messages with IDs 1 & 2 are reserved for usage with RTM Chunk Stream protocol. Protocol messages with IDs 3-6 are reserved for usage of RTMP. Protocol message with ID 7 is used between edge server and origin server. ]] if(value == 0x01) then return "Set Chunk Size"; elseif(value == 0x02) then return "Abort Message"; elseif(value == 0x03) then return "Acknowledgement"; elseif(value == 0x04) then return "User Control Message"; elseif(value == 0x05) then return "Window Acknowledgement Size"; elseif(value == 0x06) then return "Set Peer Bandwidth"; elseif(value == 0x07) then return "Edge And Origin Server Command"; --[[ 3. Types of messages The server and the client send messages over the network to communicate with each other. The messages can be of any type which includes audio messages, video messages, command messages, shared object messages, data messages, and user control messages. ]] --[[ 3.1. Command message Command messages carry the AMF-encoded commands between the client and the server. These messages have been assigned message type value of 20 for AMF0 encoding and message type value of 17 for AMF3 encoding. These messages are sent to perform some operations like connect, createStream, publish, play, pause on the peer. Command messages like onstatus, result etc. are used to inform the sender about the status of the requested commands. A command message consists of command name, transaction ID, and command object that contains related parameters. A client or a server can request Remote Procedure Calls (RPC) over streams that are communicated using the command messages to the peer. ]] elseif(value == 17) then return "AMF3 Command Message."; elseif(value == 20) then return "AMF0 Command Message."; --[[ 3.2. Data message The client or the server sends this message to send Metadata or any user data to the peer. Metadata includes details about the data(audio, video etc.) like creation time, duration, theme and so on. These messages have been assigned message type value of 18 for AMF0 and message type value of 15 for AMF3. ]] elseif(value == 18) then return "AMF3 Data Message."; elseif(value == 15) then return "AMF0 Data Message."; --[[ 3.3. Shared object message A shared object is a Flash object (a collection of name value pairs) that are in synchronization across multiple clients, instances, and so on. The message types kMsgContainer=19 for AMF0 and kMsgContainerEx=16 for AMF3 are reserved for shared object events. Each message can contain multiple events. ]] --[[ 3.4. Audio message The client or the server sends this message to send audio data to the peer. The message type value of 8 is reserved for audio messages. ]] elseif(value == 8) then return "Audio message"; --[[ 3.5. Video message The client or the server sends this message to send video data to the peer. The message type value of 9 is reserved for video messages. These messages are large and can delay the sending of other type of messages. To avoid such a situation, the video message is assigned the lowest priority. ]] elseif(value == 9) then return "Video message"; --[[ 3.6. Aggregate message An aggregate message is a single message that contains a list of submessages. The message type value of 22 is reserved for aggregate messages. ]] elseif(value == 22) then return "Aggregate message"; --[[ 3.7. User Control message The client or the server sends this message to notify the peer about the user control events. For information about the message format, refer to the User Control Messages section in the RTMP Message Foramts draft. ]] else return "Unknown Message Type"; end end --[[ print all linkups. ]] function utilityRtmpGetObjectLinkupString(rtmpObject) if(rtmpObject == nil or rtmpObject.pre == 0)then return string.format("%d", rtmpObject.id); end -- get the whole link. local previousLinks = string.format("%d(%d)", rtmpObject.id, rtmpObject.bytes:len()); local ppreviousObj = rtmpObject; while(ppreviousObj.pre > 0)do ppreviousObj = rtmpObjectPool[ppreviousObj.pre]; previousLinks = string.format("%d(%d)=>%s", ppreviousObj.id, ppreviousObj.bytes:len() - ppreviousObj.refpos, previousLinks); -- break the partial refer if(ppreviousObj.refpos > 0)then break; end end return previousLinks; end --[[ setup the linkups. ]] function utilityRtmpSetupObjectLinkups(rtmpObject) -- then setup the link of objects. local previousObject = nil; local currentId = rtmpObject.id - 1; while(currentId >= 0)do local obj = rtmpObjectPool[currentId]; if(obj ~= nil)then -- if the next of obj is uninitialized, it must has a next packet. if(obj.next == -1)then previousObject = obj; -- set the link. previousObject.next = rtmpObject.id; rtmpObject.pre = previousObject.id; debug(string.format("[oopool] setup the linkup: %s", utilityRtmpGetObjectLinkupString(rtmpObject))); end end currentId = currentId -1; end if(previousObject == nil)then rtmpObject.pre = 0; debug(string.format("[oopool] no previous pakcet found, set packet %d to single packet.", rtmpObject.id)); end end --[[ if object specified by pktNumber exists, get it, otherwise create a new object and push to pool ]] function utilityRtmpCreateOrGetObject(pkt, buf) local rtmpObject = rtmpObjectPool[pkt.number]; if(rtmpObject == nil) then -- create a new rtmp object rtmpObject = { id = pkt.number, src_port = pkt.src_port, type = -1, show = false, pre = -1, refpos = 0, next = -1, bytes = nil, packet_names = {} }; -- convert buf(Tvb) to ByteArray. local bufByteArray = buf(0, buf:len()):bytes(); rtmpObject.bytes = ByteArray.new(tostring(bufByteArray)); debug(string.format("[oopool] nil found, create a new object.")); utilityRtmpSetupObjectLinkups(rtmpObject); rtmpObjectPool[pkt.number] = rtmpObject; debug(string.format("[oopool] create a new rtmp object in oo pool. id=%d, buf=%d", rtmpObject.id, rtmpObject.bytes:len())); else debug(string.format("[oopool] get a exists object in oo pool. id=%d, buf=%d", rtmpObject.id, rtmpObject.bytes:len())); end return rtmpObject; end --[[ insert a packet name to the current object. ]] function utilityRtmpAddParsedPacketName(parsedPacketName) info(string.format("[utility] add parsed packet name to object(%d): %s", rtmpCurrentObject.id, parsedPacketName)); -- insert to the head. table.insert(rtmpCurrentObject.packet_names, 0, parsedPacketName); end --[[ append the buffer to the pool. ]] function utilityRtmpAppendBytesPool(rtmpObject) debug(string.format("[pool] start to initialize bytes pool. object.bytes=%d", rtmpObject.bytes:len())); if(rtmpObject.bytes:len() <= 0)then critical(string.format("[pool] the pakcet buffer must not be empty, actual size=%d", 0)); return; end -- clear the bytes pool and push all link bytes to pool rtmpBytesPool = ByteArray.new(); local currentObject = rtmpObject; while(currentObject ~= nil)do -- must insert the object bytes in the front of pool. rtmpBytesPool = ByteArray.new(tostring(currentObject.bytes) .. tostring(rtmpBytesPool)); currentObject = rtmpObjectPool[currentObject.pre]; -- append the partially bytes. if(currentObject ~= nil)then local refpos = currentObject.refpos; -- refer to the only previous object. if(refpos > 0)then -- append the partial bytes. local length = currentObject.bytes:len(); warn(string.format("[pool] get the partiical bytes from previous object: id=%d offset=%d length=%d part-bytes=%d", currentObject.id, refpos, length, length-refpos)); local partBytes = currentObject.bytes:subset(refpos, length - refpos); rtmpBytesPool = ByteArray.new(tostring(partBytes) .. tostring(rtmpBytesPool)); -- break the loop. currentObject = nil; end end end debug(string.format("[pool] linkup of object(%d) is %s. pool size=%d", rtmpObject.id, utilityRtmpGetObjectLinkupString(rtmpObject), rtmpBytesPool:len())); -- return the TvbRange return rtmpBytesPool:tvb(""); end --[[ whether the pakcet is from server. ]] function utilityRtmpIsFromServer(pkt) return pkt.src_port == rtmpServerPort; end --[[ discovery the sender name. return "server" if from server, otherwise "client". ]] function utilityRtmpDiscoverySender(pkt) if(utilityRtmpIsFromServer(pkt)) then return "server"; else return "client"; end end --[[ ============================================================================== ========================register protocol parser============================== ============================================================================== ]] --[[ ]] -- register to dissector table if(true) then local tcp_port_table = DissectorTable.get("tcp.port"); tcp_port_table:add(1935, rtmp); tcp_port_table:add(1985, rtmp); end end
自己解析的RTMP,用C++解析的结果:
[winlin@dev6 protocol]$ ./rtmp_mock_player Usage: ./mock_client <host> <port> <vhost> <app> <stream> host: the host to connect to. port: the port to connect to. vhost: the vhost to connect to. app: the app to connect to. stream: the stream to play. default to ./mock-client 192.168.100.145 1935 winlin.cn vod mp4:sample1_1500kbps.f4v mock client is a mock rtmp-client, such as the flash player, which is used to test the rtmp server socket init success socket connect success start handshake... [client] handshake: c0c1 sent to server. [server] handshake: s0s1s2 recv from server. [client] handshake c2 sent to server. finished! [client] connect to app: vod, tcUrl=rtmp://winlin.cn:1935/vod [client] connect to server success //Real Time Messaging Protocol (Window Acknowledgement Size 2500000) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):2(0x02) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:4(0x00000004) messageType:5(0x05) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:4(0x00000004) payload:4bytes 00 26 25 A0 [server] get a server response [client] ack size set success // Real Time Messaging Protocol (Set Peer Bandwidth 2500000,Dynamic) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):2(0x02) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:5(0x00000005) messageType:6(0x06) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:5(0x00000005) payload:5bytes 00 26 25 A0 02 [server] get a server response // Real Time Messaging Protocol (AMF0 Command _result('NetConnection.Connect.Success')) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):3(0x03) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:240(0x000000F0) messageType:20(0x14) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:128(0x00000080) payload:128bytes 02 00 07 5F 72 65 73 75 6C 74 00 3F F0 00 00 00 ... // continue........... chunk: basicHeader: chunkType(2bits):3(0x03) chunkStreamId(6bits):3(0x03) chunkMsgHeader: EmptyChunkMsgHeader:Null extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:112(0x00000070) payload:112bytes 6E 65 63 74 2E 53 75 63 63 65 73 73 00 0B 64 65 ... [server] get a server response // Real Time Messaging Protocol (AMF0 Command onBWDone()) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):3(0x03) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:21(0x00000015) messageType:20(0x14) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:21(0x00000015) payload:21bytes 02 00 08 6F 6E 42 57 44 6F 6E 65 00 00 00 00 00 ... [server] get a server response [server] no packet! [client] create stream success // For CreateStream() // Real Time Messaging Protocol (AMF0 Command _result()) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):3(0x03) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:29(0x0000001D) messageType:20(0x14) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:29(0x0000001D) payload:29bytes 02 00 07 5F 72 65 73 75 6C 74 00 40 00 00 00 00 ... [server] get a server response [client] play success // Real Time Messaging Protocol (Set Chunk Size 1024) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):2(0x02) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:4(0x00000004) messageType:1(0x01) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:4(0x00000004) payload:4bytes 00 00 04 00 [debug] set chunk buffer size to 1024 [server] get a server response [client] set buffer length success // Real Time Messaging Protocol (User Control Message Stream Is Recorded 1) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):2(0x02) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:6(0x00000006) messageType:4(0x04) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:6(0x00000006) payload:6bytes 00 04 00 00 00 01 [server] get a server response // Real Time Messaging Protocol (User Control Message Stream Begin 1) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):2(0x02) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:6(0x00000006) messageType:4(0x04) messageStreamId:0(0x00000000) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:6(0x00000006) payload:6bytes 00 00 00 00 00 01 [server] get a server response // Real Time Messaging Protocol (AMF0 Command onStatus('NetStream.Play.Reset')) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):4(0x04) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:182(0x000000B6) messageType:20(0x14) messageStreamId:1(0x00000001) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:182(0x000000B6) payload:182bytes 02 00 08 6F 6E 53 74 61 74 75 73 00 00 00 00 00 ... [server] get a server response // Real Time Messaging Protocol (AMF0 Command onStatus('NetStream.Play.Start')) chunk: basicHeader: chunkType(2bits):0(0x00) chunkStreamId(6bits):5(0x05) chunkMsgHeader: timeStampDelta:0(0x00000000) messageLength:176(0x000000B0) messageType:20(0x14) messageStreamId:1(0x00000001) extendedTimestamp: EmptyExtendedTimestamp:Null chunkData: length:176(0x000000B0) payload:176bytes 02 00 08 6F 6E 53 74 61 74 75 73 00 00 00 00 00 ..