186 lines
6.3 KiB
C++
186 lines
6.3 KiB
C++
/// \file analyser_rtmp.cpp
|
|
/// Debugging tool for RTMP data.
|
|
|
|
#include "analyser_rtmp.h"
|
|
|
|
void AnalyserRTMP::init(Util::Config &conf){
|
|
Analyser::init(conf);
|
|
JSON::Value opt;
|
|
opt["long"] = "reconstruct";
|
|
opt["short"] = "R";
|
|
opt["arg"] = "string";
|
|
opt["default"] = "";
|
|
opt["help"] = "Reconstruct FLV file from RTMP stream to the given filename";
|
|
conf.addOption("reconstruct", opt);
|
|
opt.null();
|
|
}
|
|
|
|
AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){
|
|
if (conf.getString("reconstruct") != ""){
|
|
reconstruct.open(conf.getString("reconstruct").c_str());
|
|
if (reconstruct.good()){
|
|
reconstruct.write(FLV::Header, 13);
|
|
WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AnalyserRTMP::open(const std::string & filename){
|
|
if (!Analyser::open(filename)){return false;}
|
|
// Skip the 3073 byte handshake - there is no (truly) useful data in this.
|
|
MEDIUM_MSG("Skipping handshake...");
|
|
std::string inbuffer;
|
|
inbuffer.reserve(3073);
|
|
while (std::cin.good() && inbuffer.size() < 3073){inbuffer += std::cin.get();}
|
|
RTMPStream::rec_cnt += 3073;
|
|
inbuffer.erase(0, 3073); // strip the handshake part
|
|
MEDIUM_MSG("Handshake skipped");
|
|
return true;
|
|
}
|
|
|
|
bool AnalyserRTMP::parsePacket(){
|
|
// While we can't parse a packet,
|
|
while (!next.Parse(strbuf)){
|
|
// fill our internal buffer "strbuf" in (up to) 1024 byte chunks
|
|
if (std::cin.good()){
|
|
unsigned int charCount = 0;
|
|
std::string tmpbuffer;
|
|
tmpbuffer.reserve(1024);
|
|
while (std::cin.good() && charCount < 1024){
|
|
char newchar = std::cin.get();
|
|
if (std::cin.good()){
|
|
tmpbuffer += newchar;
|
|
++read_in;
|
|
++charCount;
|
|
}
|
|
}
|
|
strbuf.append(tmpbuffer);
|
|
}else{
|
|
// if we can't fill the buffer, and have no parsable packet(s), return false
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We now know for sure that we've parsed a packet
|
|
DETAIL_HI("Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u",
|
|
next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id,
|
|
next.msg_stream_id);
|
|
switch (next.msg_type_id){
|
|
case 0: // does not exist
|
|
DETAIL_LOW("Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i", read_in - strbuf.size(),
|
|
next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
|
|
return 0;
|
|
break; // happens when connection breaks unexpectedly
|
|
case 1: // set chunk size
|
|
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
|
|
DETAIL_MED("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
|
|
break;
|
|
case 2: // abort message - we ignore this one
|
|
DETAIL_MED("CTRL: Abort message: %i", ntohl(*(int *)next.data.c_str()));
|
|
// 4 bytes of stream id to drop
|
|
break;
|
|
case 3: // ack
|
|
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
|
|
DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at);
|
|
break;
|
|
case 4:{
|
|
short int ucmtype = ntohs(*(short int *)next.data.c_str());
|
|
switch (ucmtype){
|
|
case 0:
|
|
DETAIL_MED("CTRL: User control message: stream begin %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 1:
|
|
DETAIL_MED("CTRL: User control message: stream EOF %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 2:
|
|
DETAIL_MED("CTRL: User control message: stream dry %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 3:
|
|
DETAIL_MED("CTRL: User control message: setbufferlen %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 4:
|
|
DETAIL_MED("CTRL: User control message: streamisrecorded %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 6:
|
|
DETAIL_MED("CTRL: User control message: pingrequest %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 7:
|
|
DETAIL_MED("CTRL: User control message: pingresponse %u",
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
case 31:
|
|
case 32:
|
|
// don't know, but not interesting anyway
|
|
break;
|
|
default:
|
|
DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", ucmtype,
|
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
|
break;
|
|
}
|
|
}break;
|
|
case 5: // window size of other end
|
|
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
|
|
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
|
DETAIL_MED("CTRL: Window size: %i", RTMPStream::rec_window_size);
|
|
break;
|
|
case 6:
|
|
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str());
|
|
// 4 bytes window size, 1 byte limit type (ignored)
|
|
DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size);
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
if (detail >= 4 || reconstruct.good() || validate){
|
|
F.ChunkLoader(next);
|
|
mediaTime = F.tagTime();
|
|
DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str());
|
|
if (reconstruct.good()){reconstruct.write(F.data, F.len);}
|
|
}
|
|
break;
|
|
case 15: DETAIL_MED("Received AFM3 data message"); break;
|
|
case 16: DETAIL_MED("Received AFM3 shared object"); break;
|
|
case 17:{
|
|
DETAIL_MED("Received AFM3 command message:");
|
|
char soort = next.data[0];
|
|
next.data = next.data.substr(1);
|
|
if (soort == 0){
|
|
amfdata = AMF::parse(next.data);
|
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
|
}else{
|
|
amf3data = AMF::parse3(next.data);
|
|
DETAIL_MED("%s", amf3data.Print().c_str());
|
|
}
|
|
}break;
|
|
case 18:{
|
|
DETAIL_MED("Received AFM0 data message (metadata):");
|
|
amfdata = AMF::parse(next.data);
|
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
|
if (reconstruct.good()){
|
|
F.ChunkLoader(next);
|
|
reconstruct.write(F.data, F.len);
|
|
}
|
|
}break;
|
|
case 19: DETAIL_MED("Received AFM0 shared object"); break;
|
|
case 20:{// AMF0 command message
|
|
DETAIL_MED("Received AFM0 command message:");
|
|
amfdata = AMF::parse(next.data);
|
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
|
}break;
|
|
case 22:
|
|
if (reconstruct.good()){reconstruct << next.data;}
|
|
break;
|
|
default:
|
|
FAIL_MSG(
|
|
"Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
|
return false;
|
|
break;
|
|
}// switch for type of chunk
|
|
return true;
|
|
}
|
|
|