diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 05a1c889..52d4f0e3 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -121,13 +121,13 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ } if (viddone && auddone && justdone){ if (viddata.len != 0){ - Socket.write(RTMPStream::SendMedia((unsigned char)viddata.data[0], (unsigned char *)viddata.data+11, viddata.len-15, 0)); + Socket.write(RTMPStream::SendMedia(viddata)); #if DEBUG >= 8 fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), viddata.tagTime(), viddata.tagType().c_str()); #endif } if (auddata.len != 0){ - Socket.write(RTMPStream::SendMedia((unsigned char)auddata.data[0], (unsigned char *)auddata.data+11, auddata.len-15, 0)); + Socket.write(RTMPStream::SendMedia(auddata)); #if DEBUG >= 8 fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), auddata.tagTime(), auddata.tagType().c_str()); #endif @@ -137,7 +137,7 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ //not gotten init yet? cancel this tag if (viddata.len == 0 || auddata.len == 0){break;} //send tag normally - Socket.write(RTMPStream::SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, tag.tagTime())); + Socket.write(RTMPStream::SendMedia(tag)); #if DEBUG >= 8 fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str()); #endif @@ -383,7 +383,7 @@ void Connector_RTMP::parseChunk(){ amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting...")); amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS")); - amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1)); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); #if DEBUG >= 4 amfreply.Print(); #endif @@ -397,7 +397,7 @@ void Connector_RTMP::parseChunk(){ amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!")); amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS")); - amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1)); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); #if DEBUG >= 4 amfreply.Print(); #endif diff --git a/RTMP_Parser/Makefile b/RTMP_Parser/Makefile index 95aa3b19..442e9db2 100644 --- a/RTMP_Parser/Makefile +++ b/RTMP_Parser/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp +SRC = main.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/flv_tag.cpp ../util/socket.cpp OBJ = $(SRC:.cpp=.o) OUT = RTMP_Parser INCLUDES = @@ -8,7 +8,7 @@ CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar LIBS = -lssl -lcrypto -.SUFFIXES: .cpp +.SUFFIXES: .cpp .PHONY: clean default default: $(OUT) .cpp.o: diff --git a/RTMP_Parser/main.cpp b/RTMP_Parser/main.cpp index 4f03e803..9ab21b35 100644 --- a/RTMP_Parser/main.cpp +++ b/RTMP_Parser/main.cpp @@ -2,30 +2,61 @@ /// Debugging tool for RTMP data. /// Expects RTMP data of one side of the conversion through stdin, outputs human-readable information to stderr. /// Automatically skips 3073 bytes of handshake data. +/// Optionally reconstructs an FLV file +/// Singular argument is a bitmask indicating the following (defaulting to 0): +/// - 0 = Info: Output chunk meanings and fulltext commands to stderr. +/// - 1 = Reconstruct: Output valid .flv file to stdout. +/// - 2 = Explicit: Audio/video data details. +/// - 4 = Verbose: details about every whole chunk. #define DEBUG 10 //maximum debugging level #include #include #include #include +#include "../util/flv_tag.h" #include "../util/amf.h" #include "../util/rtmpchunks.h" +int Detail = 0; +#define DETAIL_RECONSTRUCT 1 +#define DETAIL_EXPLICIT 2 +#define DETAIL_VERBOSE 4 + /// Debugging tool for RTMP data. /// Expects RTMP data of one side of the conversion through stdin, outputs human-readable information to stderr. /// Will output FLV file to stdout, if available /// Automatically skips 3073 bytes of handshake data. -int main(){ +int main(int argc, char ** argv){ + + if (argc > 1){ + Detail = atoi(argv[1]); + fprintf(stderr, "Detail level set:\n"); + if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){ + fprintf(stderr, " - Will reconstuct FLV file to stdout\n"); + std::cout.write(FLV::Header, 13); + } + if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){ + fprintf(stderr, " - Will list explicit video/audio data information\n"); + } + if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){ + fprintf(stderr, " - Will list verbose chunk information\n"); + } + } std::string inbuffer; while (std::cin.good()){inbuffer += std::cin.get();}//read all of std::cin to temp inbuffer.erase(0, 3073);//strip the handshake part RTMPStream::Chunk next; + FLV::Tag F;//FLV holder AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); - + while (next.Parse(inbuffer)){ + if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){ + fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", 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 fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id); @@ -47,28 +78,28 @@ int main(){ short int ucmtype = ntohs(*(short int*)next.data.c_str()); switch (ucmtype){ case 0: - fprintf(stderr, "CTRL: User control message: stream begin %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 1: - fprintf(stderr, "CTRL: User control message: stream EOF %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 2: - fprintf(stderr, "CTRL: User control message: stream dry %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 3: - fprintf(stderr, "CTRL: User control message: setbufferlen %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 4: - fprintf(stderr, "CTRL: User control message: streamisrecorded %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 6: - fprintf(stderr, "CTRL: User control message: pingrequest %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; case 7: - fprintf(stderr, "CTRL: User control message: pingresponse %i\n", ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); break; default: - fprintf(stderr, "CTRL: User control message: UNKNOWN %hi - %i\n", ucmtype, ntohl(*(int*)next.data.c_str()+2)); + fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2))); break; } } break; @@ -83,10 +114,28 @@ int main(){ fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size); break; case 8: - fprintf(stderr, "Received %i bytes audio data\n", next.len); + if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){ + F.ChunkLoader(next); + if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){ + fprintf(stderr, "Received %i bytes audio data\n", next.len); + std::cerr << "Got a " << F.len << " bytes " << F.tagType() << " FLV tag of time " << F.tagTime() << "." << std::endl; + } + if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){ + std::cout.write(F.data, F.len); + } + } break; case 9: - fprintf(stderr, "Received %i bytes video data\n", next.len); + if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){ + F.ChunkLoader(next); + if ((Detail & DETAIL_EXPLICIT) == DETAIL_EXPLICIT){ + fprintf(stderr, "Received %i bytes video data\n", next.len); + std::cerr << "Got a " << F.len << " bytes " << F.tagType() << " FLV tag of time " << F.tagTime() << "." << std::endl; + } + if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){ + std::cout.write(F.data, F.len); + } + } break; case 15: fprintf(stderr, "Received AFM3 data message\n"); @@ -110,6 +159,10 @@ int main(){ fprintf(stderr, "Received AFM0 data message (metadata):\n"); amfdata = AMF::parse(next.data); amfdata.Print(); + if ((Detail & DETAIL_RECONSTRUCT) == DETAIL_RECONSTRUCT){ + F.ChunkLoader(next); + std::cout.write(F.data, F.len); + } } break; case 19: fprintf(stderr, "Received AFM0 shared object\n"); diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index 3be1f4e1..526de7d0 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -2,13 +2,17 @@ /// Holds all code for the FLV namespace. #include "flv_tag.h" +#include "rtmpchunks.h" #include //for Tag::FileLoader #include //for Tag::FileLoader #include //for Tag::FileLoader #include //malloc #include //memcpy -char FLV::Header[13]; ///< Holds the last FLV header parsed. +/// Holds the last FLV header parsed. +/// Defaults to a audio+video header on FLV version 0x01 if no header received yet. +char FLV::Header[13] = {'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09, 0, 0, 0, 0}; + bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV. std::string FLV::Error_Str = ""; @@ -209,6 +213,16 @@ FLV::Tag::Tag(const Tag& O){ isKeyframe = O.isKeyframe; }//copy constructor + +/// Copy constructor from a RTMP chunk. +/// Copies the contents of a RTMP chunk into a valid FLV tag. +/// Exactly the same as making a chunk by through the default (empty) constructor +/// and then calling FLV::Tag::ChunkLoader with the chunk as argument. +FLV::Tag::Tag(const RTMPStream::Chunk& O){ + len = 0; buf = 0; data = 0; isKeyframe = false; done = true; sofar = 0; + ChunkLoader(O); +} + /// Assignment operator - works exactly like the copy constructor. /// This operator checks for self-assignment. FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){ @@ -231,6 +245,32 @@ FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){ return *this; }//assignment operator +/// FLV loader function from chunk. +/// Copies the contents and wraps it in a FLV header. +bool FLV::Tag::ChunkLoader(const RTMPStream::Chunk& O){ + len = O.len + 15; + if (len > 0){ + if (!data){ + data = (char*)malloc(len); + buf = len; + }else{ + if (buf < len){ + data = (char*)realloc(data, len); + buf = len; + } + } + memcpy(data+11, &(O.data[0]), O.len); + } + ((unsigned int *)(data+len-4))[0] = O.len; + data[0] = O.msg_type_id; + data[3] = O.len & 0xFF; + data[2] = (O.len >> 8) & 0xFF; + data[1] = (O.len >> 16) & 0xFF; + tagTime(O.timestamp); + return true; +} + + /// Helper function for FLV::MemLoader. /// This function will try to read count bytes from data buffer D into buffer. /// This function should be called repeatedly until true. diff --git a/util/flv_tag.h b/util/flv_tag.h index 1d7bbc41..1350c870 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -5,6 +5,11 @@ #include "socket.h" #include +//forward declaration of RTMPStream::Chunk to avoid circular dependencies. +namespace RTMPStream{ + class Chunk; +}; + /// This namespace holds all FLV-parsing related functionality. namespace FLV { //variables @@ -30,7 +35,9 @@ namespace FLV { Tag(); ///< Constructor for a new, empty, tag. Tag(const Tag& O); ///< Copy constructor, copies the contents of an existing tag. Tag & operator= (const Tag& O); ///< Assignment operator - works exactly like the copy constructor. + Tag(const RTMPStream::Chunk& O); /// 0) && (prev.cs_id == cs_id)){ if (msg_stream_id == prev.msg_stream_id){ chtype = 0x40;//do not send msg_stream_id @@ -54,6 +55,8 @@ std::string RTMPStream::Chunk::Pack(){ } } } + //override - we always sent type 0x00 if the timestamp has decreased since last chunk in this channel + if (timestamp < prev.timestamp){chtype = 0x00;} } if (cs_id <= 63){ output += (unsigned char)(chtype | cs_id); @@ -76,15 +79,15 @@ std::string RTMPStream::Chunk::Pack(){ tmpi = timestamp - prev.timestamp; } if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} - output += (unsigned char)(tmpi / (256*256)); - output += (unsigned char)(tmpi / 256); - output += (unsigned char)(tmpi % 256); + output += (unsigned char)((tmpi >> 16) & 0xff); + output += (unsigned char)((tmpi >> 8) & 0xff); + output += (unsigned char)(tmpi & 0xff); if (chtype != 0x80){ //len tmpi = len; - output += (unsigned char)(tmpi / (256*256)); - output += (unsigned char)(tmpi / 256); - output += (unsigned char)(tmpi % 256); + output += (unsigned char)((tmpi >> 16) & 0xff); + output += (unsigned char)((tmpi >> 8) & 0xff); + output += (unsigned char)(tmpi & 0xff); //msg type id output += (unsigned char)msg_type_id; if (chtype != 0x40){ @@ -98,10 +101,10 @@ std::string RTMPStream::Chunk::Pack(){ } //support for 0x00ffffff timestamps if (ntime){ - output += (unsigned char)(ntime % 256); - output += (unsigned char)(ntime / 256); - output += (unsigned char)(ntime / (256*256)); - output += (unsigned char)(ntime / (256*256*256)); + output += (unsigned char)(ntime & 0xff); + output += (unsigned char)((ntime >> 8) & 0xff); + output += (unsigned char)((ntime >> 16) & 0xff); + output += (unsigned char)((ntime >> 24) & 0xff); } len_left = 0; while (len_left < len){ @@ -162,7 +165,7 @@ std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, /// \param ts Timestamp of the media data, relative to current system time. std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ RTMPStream::Chunk ch; - ch.cs_id = msg_type_id; + ch.cs_id = msg_type_id+42; ch.timestamp = ts; ch.len = len; ch.real_len = len; @@ -173,6 +176,21 @@ std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * dat return ch.Pack(); }//SendMedia +/// Packs up a chunk with media contents. +/// \param tag FLV::Tag with media to send. +std::string RTMPStream::SendMedia(FLV::Tag & tag){ + RTMPStream::Chunk ch; + ch.cs_id = ((unsigned char)tag.data[0]); + ch.timestamp = tag.tagTime(); + ch.len = tag.len-15; + ch.real_len = tag.len-15; + ch.len_left = 0; + ch.msg_type_id = (unsigned char)tag.data[0]; + ch.msg_stream_id = 1; + ch.data.append(tag.data+11, (size_t)(tag.len-15)); + return ch.Pack(); +}//SendMedia + /// Packs up a chunk for a control message with 1 argument. std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){ RTMPStream::Chunk ch; @@ -199,7 +217,7 @@ std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned ch.msg_type_id = type; ch.msg_stream_id = 0; ch.data.resize(5); - *(int*)((char*)ch.data.c_str()) = htonl(data); + *(unsigned int*)((char*)ch.data.c_str()) = htonl(data); ch.data[4] = data2; return ch.Pack(); }//SendCTL @@ -215,7 +233,7 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){ ch.msg_type_id = 4; ch.msg_stream_id = 0; ch.data.resize(6); - *(int*)((char*)ch.data.c_str()+2) = htonl(data); + *(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data); ch.data[0] = 0; ch.data[1] = type; return ch.Pack(); @@ -232,8 +250,8 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned ch.msg_type_id = 4; ch.msg_stream_id = 0; ch.data.resize(10); - *(int*)((char*)ch.data.c_str()+2) = htonl(data); - *(int*)((char*)ch.data.c_str()+6) = htonl(data2); + *(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data); + *(unsigned int*)(((char*)ch.data.c_str())+6) = htonl(data2); ch.data[0] = 0; ch.data[1] = type; return ch.Pack(); @@ -270,11 +288,12 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ cs_id = chunktype & 0x3F; break; } - + RTMPStream::Chunk prev = lastrecv[cs_id]; //process the rest of the header, for each chunk type - switch (chunktype & 0xC0){ + headertype = chunktype & 0xC0; + switch (headertype){ case 0x00: if (indata.size() < i+11) return false; //can't read whole header timestamp = indata[i++]*256*256; @@ -296,7 +315,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; - timestamp += prev.timestamp; + if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = indata[i++]*256*256; len += indata[i++]*256; len += indata[i++]; @@ -310,7 +329,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; - timestamp += prev.timestamp; + if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = prev.len; len_left = prev.len_left; msg_type_id = prev.msg_type_id; @@ -344,7 +363,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ timestamp += indata[i++]*256; timestamp += indata[i++]; } - + //read data if length > 0, and allocate it if (real_len > 0){ if (prev.len_left > 0){ @@ -397,22 +416,22 @@ bool RTMPStream::doHandshake(){ uint8_t _validationScheme = 5; if (ValidateClientScheme(Client, 0)) _validationScheme = 0; if (ValidateClientScheme(Client, 1)) _validationScheme = 1; - + #if DEBUG >= 4 fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off"); #endif - + //FIRST 1536 bytes from server response //compute DH key position uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme); uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme); - + //generate DH key DHWrapper dhWrapper(1024); if (!dhWrapper.Initialize()) return false; if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false; if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false; - + if (encrypted) { uint8_t secretKey[128]; if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false; @@ -433,7 +452,7 @@ bool RTMPStream::doHandshake(){ memcpy(Server + serverDigestOffset, pTempHash, 32); delete[] pTempBuffer; delete[] pTempHash; - + //SECOND 1536 bytes from server response uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); pTempHash = new uint8_t[512]; diff --git a/util/rtmpchunks.h b/util/rtmpchunks.h index 8feeefb0..ff4eee4a 100644 --- a/util/rtmpchunks.h +++ b/util/rtmpchunks.h @@ -9,6 +9,11 @@ #include #include +//forward declaration of FLV::Tag to avoid circular dependencies. +namespace FLV{ + class Tag; +}; + /// Contains all functions and classes needed for RTMP connections. namespace RTMPStream{ @@ -30,6 +35,7 @@ namespace RTMPStream{ /// Holds a single RTMP chunk, either send or receive direction. class Chunk{ public: + unsigned char headertype; ///< For input chunks, the type of header. This is calculated automatically for output chunks. unsigned int cs_id; ///< ContentStream ID unsigned int timestamp; ///< Timestamp of this chunk. unsigned int len; ///< Length of the complete chunk. @@ -50,6 +56,7 @@ namespace RTMPStream{ std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data); std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts); + std::string SendMedia(FLV::Tag & tag); std::string SendCTL(unsigned char type, unsigned int data); std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2); std::string SendUSR(unsigned char type, unsigned int data);