/// \file rtmpchunks.cpp /// Holds all code for the RTMPStream namespace. #include "rtmpchunks.h" #include "flv_tag.h" #include "timing.h" char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake std::string RTMPStream::handshake_in; ///< Input for the handshake. std::string RTMPStream::handshake_out;///< Output for the handshake. unsigned int RTMPStream::chunk_rec_max = 128; unsigned int RTMPStream::chunk_snd_max = 128; unsigned int RTMPStream::rec_window_size = 2500000; unsigned int RTMPStream::snd_window_size = 2500000; unsigned int RTMPStream::rec_window_at = 0; unsigned int RTMPStream::snd_window_at = 0; unsigned int RTMPStream::rec_cnt = 0; unsigned int RTMPStream::snd_cnt = 0; timeval RTMPStream::lastrec; /// Holds the last sent chunk for every msg_id. std::map RTMPStream::Chunk::lastsend; /// Holds the last received chunk for every msg_id. std::map RTMPStream::Chunk::lastrecv; #include #include #include #include #include #include #include #include #define P1024 \ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" uint8_t genuineFMSKey[] = { 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c, 0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, // Genuine Adobe Flash Media Server 001 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae }; // 68 uint8_t genuineFPKey[] = { 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c, 0x61, 0x73, 0x68, 0x20, 0x50, 0x6c, 0x61, 0x79, // Genuine Adobe Flash Player 001 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae }; // 62 inline uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme){ if (scheme == 0){ return ((pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11]) % 728) + 12; }else{ return ((pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775]) % 728) + 776; } } inline uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme){ if (scheme == 0){ return ((pBuffer[1532] + pBuffer[1533] + pBuffer[1534] + pBuffer[1535]) % 632) + 772; }else{ return ((pBuffer[768] + pBuffer[769] + pBuffer[770] + pBuffer[771]) % 632) + 8; } } class DHWrapper { private: int32_t _bitsCount; DH *_pDH; uint8_t *_pSharedKey; int32_t _sharedKeyLength; BIGNUM *_peerPublickey; public: DHWrapper(int32_t bitsCount); virtual ~DHWrapper(); bool Initialize(); bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); bool CopySharedKey(uint8_t *pDst, int32_t dstLength); private: void Cleanup(); bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); }; DHWrapper::DHWrapper(int32_t bitsCount) { _bitsCount = bitsCount; _pDH = 0; _pSharedKey = 0; _sharedKeyLength = 0; _peerPublickey = 0; } DHWrapper::~DHWrapper() { Cleanup(); } bool DHWrapper::Initialize() { Cleanup(); _pDH = DH_new(); if (!_pDH){Cleanup(); return false;} _pDH->p = BN_new(); if (!_pDH->p){Cleanup(); return false;} _pDH->g = BN_new(); if (!_pDH->g){Cleanup(); return false;} if (BN_hex2bn(&_pDH->p, P1024) == 0){Cleanup(); return false;} if (BN_set_word(_pDH->g, 2) != 1){Cleanup(); return false;} _pDH->length = _bitsCount; if (DH_generate_key(_pDH) != 1){Cleanup(); return false;} return true; } bool DHWrapper::CopyPublicKey(uint8_t *pDst, int32_t dstLength) { if (!_pDH){return false;} return CopyKey(_pDH->pub_key, pDst, dstLength); } bool DHWrapper::CopyPrivateKey(uint8_t *pDst, int32_t dstLength) { if (!_pDH){return false;} return CopyKey(_pDH->priv_key, pDst, dstLength); } bool DHWrapper::CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length) { if (!_pDH){return false;} if (_sharedKeyLength != 0 || _pSharedKey){return false;} _sharedKeyLength = DH_size(_pDH); if (_sharedKeyLength <= 0 || _sharedKeyLength > 1024){return false;} _pSharedKey = new uint8_t[_sharedKeyLength]; _peerPublickey = BN_bin2bn(pPeerPublicKey, length, 0); if (!_peerPublickey){return false;} if (DH_compute_key(_pSharedKey, _peerPublickey, _pDH) != _sharedKeyLength){return false;} return true; } bool DHWrapper::CopySharedKey(uint8_t *pDst, int32_t dstLength) { if (!_pDH){return false;} if (dstLength != _sharedKeyLength){return false;} memcpy(pDst, _pSharedKey, _sharedKeyLength); return true; } void DHWrapper::Cleanup() { if (_pDH){ if (_pDH->p){BN_free(_pDH->p); _pDH->p = 0;} if (_pDH->g){BN_free(_pDH->g); _pDH->g = 0;} DH_free(_pDH); _pDH = 0; } if (_pSharedKey){delete[] _pSharedKey; _pSharedKey = 0;} _sharedKeyLength = 0; if (_peerPublickey){BN_free(_peerPublickey); _peerPublickey = 0;} } bool DHWrapper::CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength) { int32_t keySize = BN_num_bytes(pNum); if ((keySize <= 0) || (dstLength <= 0) || (keySize > dstLength)){return false;} if (BN_bn2bin(pNum, pDst) != keySize){return false;} return true; } void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut) { uint8_t digest[SHA256_DIGEST_LENGTH]; unsigned int digestLen = 0; HMAC_CTX ctx; HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0); HMAC_Update(&ctx, pubKeyIn, 128); HMAC_Final(&ctx, digest, &digestLen); HMAC_CTX_cleanup(&ctx); RC4_set_key(rc4keyOut, 16, digest); HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0); HMAC_Update(&ctx, pubKeyOut, 128); HMAC_Final(&ctx, digest, &digestLen); HMAC_CTX_cleanup(&ctx); RC4_set_key(rc4keyIn, 16, digest); } void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult) { unsigned int digestLen; HMAC_CTX ctx; HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, (unsigned char*) pKey, keyLength, EVP_sha256(), 0); HMAC_Update(&ctx, (unsigned char *) pData, dataLength); HMAC_Final(&ctx, (unsigned char *) pResult, &digestLen); HMAC_CTX_cleanup(&ctx); } bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme) { uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme); uint8_t *pTempBuffer = new uint8_t[1536 - 32]; memcpy(pTempBuffer, pBuffer, clientDigestOffset); memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32, 1536 - clientDigestOffset - 32); uint8_t *pTempHash = new uint8_t[512]; HMACsha256(pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash); bool result = (memcmp(pBuffer+clientDigestOffset, pTempHash, 32) == 0); #if DEBUG >= 4 fprintf(stderr, "Client scheme validation %hhi %s\n", scheme, result?"success":"failed"); #endif delete[] pTempBuffer; delete[] pTempHash; return result; } /// Packs up the chunk for sending over the network. /// \warning Do not call if you are not actually sending the resulting data! /// \returns A std::string ready to be sent. std::string & RTMPStream::Chunk::Pack(){ static std::string output; output.clear(); RTMPStream::Chunk prev = lastsend[cs_id]; unsigned int tmpi; unsigned char chtype = 0x00; if ((prev.msg_type_id > 0) && (prev.cs_id == cs_id)){ if (msg_stream_id == prev.msg_stream_id){ chtype = 0x40;//do not send msg_stream_id if (len == prev.len){ if (msg_type_id == prev.msg_type_id){ chtype = 0x80;//do not send len and msg_type_id if (timestamp == prev.timestamp){ chtype = 0xC0;//do not send timestamp } } } } //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); }else{ if (cs_id <= 255+64){ output += (unsigned char)(chtype | 0); output += (unsigned char)(cs_id - 64); }else{ output += (unsigned char)(chtype | 1); output += (unsigned char)((cs_id - 64) % 256); output += (unsigned char)((cs_id - 64) / 256); } } unsigned int ntime = 0; if (chtype != 0xC0){ //timestamp or timestamp diff if (chtype == 0x00){ tmpi = timestamp; }else{ tmpi = timestamp - prev.timestamp; } if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} 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 >> 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){ //msg stream id output += (unsigned char)(msg_stream_id % 256); output += (unsigned char)(msg_stream_id / 256); output += (unsigned char)(msg_stream_id / (256*256)); output += (unsigned char)(msg_stream_id / (256*256*256)); } } } //support for 0x00ffffff timestamps if (ntime){ 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){ tmpi = len - len_left; if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;} output.append(data, len_left, tmpi); len_left += tmpi; if (len_left < len){ if (cs_id <= 63){ output += (unsigned char)(0xC0 + cs_id); }else{ if (cs_id <= 255+64){ output += (unsigned char)(0xC0); output += (unsigned char)(cs_id - 64); }else{ output += (unsigned char)(0xC1); output += (unsigned char)((cs_id - 64) % 256); output += (unsigned char)((cs_id - 64) / 256); } } } } lastsend[cs_id] = *this; RTMPStream::snd_cnt += output.size(); return output; }//SendChunk /// Default contructor, creates an empty chunk with all values initialized to zero. RTMPStream::Chunk::Chunk(){ cs_id = 0; timestamp = 0; len = 0; real_len = 0; len_left = 0; msg_type_id = 0; msg_stream_id = 0; data = ""; }//constructor /// Packs up a chunk with the given arguments as properties. std::string & RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ static RTMPStream::Chunk ch; ch.cs_id = cs_id; ch.timestamp = Util::epoch(); ch.len = data.size(); ch.real_len = data.size(); ch.len_left = 0; ch.msg_type_id = msg_type_id; ch.msg_stream_id = msg_stream_id; ch.data = data; return ch.Pack(); }//constructor /// Packs up a chunk with media contents. /// \param msg_type_id Type number of the media, as per FLV standard. /// \param data Contents of the media data. /// \param len Length of the media data, in bytes. /// \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){ static RTMPStream::Chunk ch; ch.cs_id = msg_type_id+42; ch.timestamp = ts; ch.len = len; ch.real_len = len; ch.len_left = 0; ch.msg_type_id = msg_type_id; ch.msg_stream_id = 1; ch.data = std::string((char*)data, (size_t)len); 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){ static 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 = std::string(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){ static RTMPStream::Chunk ch; ch.cs_id = 2; ch.timestamp = Util::epoch(); ch.len = 4; ch.real_len = 4; ch.len_left = 0; ch.msg_type_id = type; ch.msg_stream_id = 0; ch.data.resize(4); *(int*)((char*)ch.data.c_str()) = htonl(data); return ch.Pack(); }//SendCTL /// Packs up a chunk for a control message with 2 arguments. std::string & RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){ static RTMPStream::Chunk ch; ch.cs_id = 2; ch.timestamp = Util::epoch(); ch.len = 5; ch.real_len = 5; ch.len_left = 0; ch.msg_type_id = type; ch.msg_stream_id = 0; ch.data.resize(5); *(unsigned int*)((char*)ch.data.c_str()) = htonl(data); ch.data[4] = data2; return ch.Pack(); }//SendCTL /// Packs up a chunk for a user control message with 1 argument. std::string & RTMPStream::SendUSR(unsigned char type, unsigned int data){ static RTMPStream::Chunk ch; ch.cs_id = 2; ch.timestamp = Util::epoch(); ch.len = 6; ch.real_len = 6; ch.len_left = 0; ch.msg_type_id = 4; ch.msg_stream_id = 0; ch.data.resize(6); *(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data); ch.data[0] = 0; ch.data[1] = type; return ch.Pack(); }//SendUSR /// Packs up a chunk for a user control message with 2 arguments. std::string & RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){ static RTMPStream::Chunk ch; ch.cs_id = 2; ch.timestamp = Util::epoch(); ch.len = 10; ch.real_len = 10; ch.len_left = 0; ch.msg_type_id = 4; ch.msg_stream_id = 0; ch.data.resize(10); *(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(); }//SendUSR /// Parses the argument string into the current chunk. /// Tries to read a whole chunk, removing data from the input string as it reads. /// If only part of a chunk is read, it will remove the part and call itself again. /// This has the effect of only causing a "true" reponse in the case a *whole* chunk /// is read, not just part of a chunk. /// \param indata The input string to parse and update. /// \warning This function will destroy the current data in this chunk! /// \returns True if a whole chunk could be read, false otherwise. bool RTMPStream::Chunk::Parse(std::string & indata){ gettimeofday(&RTMPStream::lastrec, 0); unsigned int i = 0; if (indata.size() < 1) return false;//need at least a byte unsigned char chunktype = indata[i++]; //read the chunkstream ID properly switch (chunktype & 0x3F){ case 0: if (indata.size() < 2) return false;//need at least 2 bytes to continue cs_id = indata[i++] + 64; break; case 1: if (indata.size() < 3) return false;//need at least 3 bytes to continue cs_id = indata[i++] + 64; cs_id += indata[i++] * 256; break; default: cs_id = chunktype & 0x3F; break; } RTMPStream::Chunk prev = lastrecv[cs_id]; //process the rest of the header, for each chunk type headertype = chunktype & 0xC0; switch (headertype){ case 0x00: if (indata.size() < i+11) return false; //can't read whole header timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; len = indata[i++]*256*256; len += indata[i++]*256; len += indata[i++]; len_left = 0; msg_type_id = indata[i++]; msg_stream_id = indata[i++]; msg_stream_id += indata[i++]*256; msg_stream_id += indata[i++]*256*256; msg_stream_id += indata[i++]*256*256*256; break; case 0x40: if (indata.size() < i+7) return false; //can't read whole header if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x40 with no valid previous chunk!\n");} timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = indata[i++]*256*256; len += indata[i++]*256; len += indata[i++]; len_left = 0; msg_type_id = indata[i++]; msg_stream_id = prev.msg_stream_id; break; case 0x80: if (indata.size() < i+3) return false; //can't read whole header if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x80 with no valid previous chunk!\n");} timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = prev.len; len_left = prev.len_left; msg_type_id = prev.msg_type_id; msg_stream_id = prev.msg_stream_id; break; case 0xC0: if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0xC0 with no valid previous chunk!\n");} timestamp = prev.timestamp; len = prev.len; len_left = prev.len_left; msg_type_id = prev.msg_type_id; msg_stream_id = prev.msg_stream_id; break; } //calculate chunk length, real length, and length left till complete if (len_left > 0){ real_len = len_left; len_left -= real_len; }else{ real_len = len; } if (real_len > RTMPStream::chunk_rec_max){ len_left += real_len - RTMPStream::chunk_rec_max; real_len = RTMPStream::chunk_rec_max; } //read extended timestamp, if neccesary if (timestamp == 0x00ffffff){ if (indata.size() < i+4) return false; //can't read whole header timestamp = indata[i++]*256*256*256; timestamp += indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; } //read data if length > 0, and allocate it if (real_len > 0){ if (prev.len_left > 0){ data = prev.data; }else{ data = ""; } if (indata.size() < i+real_len) return false;//can't read all data (yet) data.append(indata, i, real_len); indata = indata.substr(i+real_len); lastrecv[cs_id] = *this; RTMPStream::rec_cnt += i+real_len; if (len_left == 0){ return true; }else{ return Parse(indata); } }else{ data = ""; indata = indata.substr(i+real_len); lastrecv[cs_id] = *this; RTMPStream::rec_cnt += i+real_len; return true; } }//Parse /// Parses the argument string into the current chunk. /// Tries to read a whole chunk, removing data from the input as it reads. /// If only part of a chunk is read, it will remove the part and call itself again. /// This has the effect of only causing a "true" reponse in the case a *whole* chunk /// is read, not just part of a chunk. /// \param indata The input string to parse and update. /// \warning This function will destroy the current data in this chunk! /// \returns True if a whole chunk could be read, false otherwise. bool RTMPStream::Chunk::Parse(Socket::Buffer & buffer){ gettimeofday(&RTMPStream::lastrec, 0); unsigned int i = 0; if (!buffer.available(3)){return false;}//we want at least 3 bytes std::string indata = buffer.copy(3); unsigned char chunktype = indata[i++]; //read the chunkstream ID properly switch (chunktype & 0x3F){ case 0: cs_id = indata[i++] + 64; break; case 1: cs_id = indata[i++] + 64; cs_id += indata[i++] * 256; break; default: cs_id = chunktype & 0x3F; break; } RTMPStream::Chunk prev = lastrecv[cs_id]; //process the rest of the header, for each chunk type headertype = chunktype & 0xC0; switch (headertype){ case 0x00: if (!buffer.available(i+11)){return false;} //can't read whole header indata = buffer.copy(i+11); timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; len = indata[i++]*256*256; len += indata[i++]*256; len += indata[i++]; len_left = 0; msg_type_id = indata[i++]; msg_stream_id = indata[i++]; msg_stream_id += indata[i++]*256; msg_stream_id += indata[i++]*256*256; msg_stream_id += indata[i++]*256*256*256; break; case 0x40: if (!buffer.available(i+7)){return false;} //can't read whole header indata = buffer.copy(i+7); if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x40 with no valid previous chunk!\n");} timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = indata[i++]*256*256; len += indata[i++]*256; len += indata[i++]; len_left = 0; msg_type_id = indata[i++]; msg_stream_id = prev.msg_stream_id; break; case 0x80: if (!buffer.available(i+3)){return false;} //can't read whole header indata = buffer.copy(i+3); if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x80 with no valid previous chunk!\n");} timestamp = indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; if (timestamp != 0x00ffffff){timestamp += prev.timestamp;} len = prev.len; len_left = prev.len_left; msg_type_id = prev.msg_type_id; msg_stream_id = prev.msg_stream_id; break; case 0xC0: if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0xC0 with no valid previous chunk!\n");} timestamp = prev.timestamp; len = prev.len; len_left = prev.len_left; msg_type_id = prev.msg_type_id; msg_stream_id = prev.msg_stream_id; break; } //calculate chunk length, real length, and length left till complete if (len_left > 0){ real_len = len_left; len_left -= real_len; }else{ real_len = len; } if (real_len > RTMPStream::chunk_rec_max){ len_left += real_len - RTMPStream::chunk_rec_max; real_len = RTMPStream::chunk_rec_max; } //read extended timestamp, if neccesary if (timestamp == 0x00ffffff){ if (!buffer.available(i+4)){return false;} //can't read timestamp indata = buffer.copy(i+4); timestamp = indata[i++]*256*256*256; timestamp += indata[i++]*256*256; timestamp += indata[i++]*256; timestamp += indata[i++]; } //read data if length > 0, and allocate it if (real_len > 0){ if (!buffer.available(i+real_len)){return false;}//can't read all data (yet) buffer.remove(i);//remove the header if (prev.len_left > 0){ data = prev.data + buffer.remove(real_len);//append the data and remove from buffer }else{ data = buffer.remove(real_len);//append the data and remove from buffer } lastrecv[cs_id] = *this; RTMPStream::rec_cnt += i+real_len; if (len_left == 0){ return true; }else{ return Parse(buffer); } }else{ buffer.remove(i);//remove the header data = ""; indata = indata.substr(i+real_len); lastrecv[cs_id] = *this; RTMPStream::rec_cnt += i+real_len; return true; } }//Parse /// Does the handshake. Expects handshake_in to be filled, and fills handshake_out. /// After calling this function, don't forget to read and ignore 1536 extra bytes, /// these are the handshake response and not interesting for us because we don't do client /// verification. bool RTMPStream::doHandshake(){ char Version; //Read C0 Version = RTMPStream::handshake_in[0]; uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1; RTMPStream::handshake_out.resize(3073); uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1; RTMPStream::rec_cnt += 1537; //Build S1 Packet *((uint32_t*)Server) = 0;//time zero *(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4 for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data bool encrypted = (Version == 6); #if DEBUG >= 4 fprintf(stderr, "Handshake version is %hhi\n", Version); #endif 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; RC4_KEY _pKeyIn; RC4_KEY _pKeyOut; InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut); uint8_t data[1536]; RC4(&_pKeyIn, 1536, data, data); RC4(&_pKeyOut, 1536, data, data); } //generate the digest uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); uint8_t *pTempBuffer = new uint8_t[1536 - 32]; memcpy(pTempBuffer, Server, serverDigestOffset); memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32); uint8_t *pTempHash = new uint8_t[512]; HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash); 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]; HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); uint8_t *pLastHash = new uint8_t[512]; HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash); memcpy(Server + 1536 * 2 - 32, pLastHash, 32); delete[] pTempHash; delete[] pLastHash; //DONE BUILDING THE RESPONSE ***// Server[-1] = Version; RTMPStream::snd_cnt += 3073; return true; }